Browse Source
Update post view layout and a height related bug. Use exo's internal layout for controls.
renovate/org.robolectric-robolectric-4.x
Update post view layout and a height related bug. Use exo's internal layout for controls.
renovate/org.robolectric-robolectric-4.x
Ammar Githam
4 years ago
20 changed files with 1175 additions and 703 deletions
-
11app/src/main/java/awais/instagrabber/activities/MainActivity.java
-
8app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemAnimatedMediaViewHolder.java
-
8app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemMediaShareViewHolder.java
-
9app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemMediaViewHolder.java
-
8app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemRavenMediaViewHolder.java
-
9app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemStoryShareViewHolder.java
-
8app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemXmaViewHolder.java
-
157app/src/main/java/awais/instagrabber/customviews/FormattedNumberTextView.java
-
56app/src/main/java/awais/instagrabber/customviews/VideoPlayerViewHelper.java
-
320app/src/main/java/awais/instagrabber/customviews/helpers/ChangeText.java
-
369app/src/main/java/awais/instagrabber/fragments/PostViewV2Fragment.java
-
91app/src/main/java/awais/instagrabber/utils/NullSafePair.java
-
75app/src/main/java/awais/instagrabber/utils/NumberUtils.java
-
10app/src/main/res/drawable/ic_bookmark.xml
-
10app/src/main/res/drawable/ic_round_bookmark_border_24.xml
-
395app/src/main/res/layout/dialog_post_view.xml
-
107app/src/main/res/layout/layout_exo_custom_controls.xml
-
203app/src/main/res/layout/layout_post_view_bottom.xml
-
20app/src/main/res/layout/layout_video_player_with_thumbnail.xml
-
4app/src/main/res/values/drawables.xml
@ -0,0 +1,157 @@ |
|||||
|
package awais.instagrabber.customviews; |
||||
|
|
||||
|
import android.content.Context; |
||||
|
import android.util.AttributeSet; |
||||
|
import android.util.Log; |
||||
|
import android.view.ViewGroup; |
||||
|
|
||||
|
import androidx.annotation.NonNull; |
||||
|
import androidx.annotation.Nullable; |
||||
|
import androidx.appcompat.widget.AppCompatTextView; |
||||
|
import androidx.transition.TransitionManager; |
||||
|
|
||||
|
import java.time.Duration; |
||||
|
|
||||
|
import awais.instagrabber.customviews.helpers.ChangeText; |
||||
|
import awais.instagrabber.utils.NumberUtils; |
||||
|
|
||||
|
public class FormattedNumberTextView extends AppCompatTextView { |
||||
|
private static final String TAG = FormattedNumberTextView.class.getSimpleName(); |
||||
|
private static final ChangeText TRANSITION = new ChangeText().setChangeBehavior(ChangeText.CHANGE_BEHAVIOR_OUT_IN); |
||||
|
|
||||
|
private long number = Long.MIN_VALUE; |
||||
|
private boolean showAbbreviation = true; |
||||
|
private boolean animateChanges = true; |
||||
|
private boolean toggleOnClick = true; |
||||
|
private boolean autoToggleToAbbreviation = true; |
||||
|
private long autoToggleTimeoutMs = Duration.ofSeconds(2).toMillis(); |
||||
|
private boolean initDone = false; |
||||
|
|
||||
|
|
||||
|
public FormattedNumberTextView(@NonNull final Context context) { |
||||
|
super(context); |
||||
|
init(); |
||||
|
} |
||||
|
|
||||
|
public FormattedNumberTextView(@NonNull final Context context, @Nullable final AttributeSet attrs) { |
||||
|
super(context, attrs); |
||||
|
init(); |
||||
|
} |
||||
|
|
||||
|
public FormattedNumberTextView(@NonNull final Context context, @Nullable final AttributeSet attrs, final int defStyleAttr) { |
||||
|
super(context, attrs, defStyleAttr); |
||||
|
init(); |
||||
|
} |
||||
|
|
||||
|
private void init() { |
||||
|
if (initDone) return; |
||||
|
setupClickToggle(); |
||||
|
initDone = true; |
||||
|
} |
||||
|
|
||||
|
private void setupClickToggle() { |
||||
|
setOnClickListener(null); |
||||
|
} |
||||
|
|
||||
|
private OnClickListener getWrappedClickListener(@Nullable final OnClickListener l) { |
||||
|
if (!toggleOnClick) { |
||||
|
return l; |
||||
|
} |
||||
|
return v -> { |
||||
|
toggleAbbreviation(); |
||||
|
if (l != null) { |
||||
|
l.onClick(this); |
||||
|
} |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
public void setNumber(final long number) { |
||||
|
if (this.number == number) return; |
||||
|
this.number = number; |
||||
|
format(); |
||||
|
} |
||||
|
|
||||
|
public void clearNumber() { |
||||
|
if (number == Long.MIN_VALUE) return; |
||||
|
number = Long.MIN_VALUE; |
||||
|
format(); |
||||
|
} |
||||
|
|
||||
|
public void setShowAbbreviation(final boolean showAbbreviation) { |
||||
|
if (this.showAbbreviation && showAbbreviation) return; |
||||
|
this.showAbbreviation = showAbbreviation; |
||||
|
format(); |
||||
|
} |
||||
|
|
||||
|
public boolean isShowAbbreviation() { |
||||
|
return showAbbreviation; |
||||
|
} |
||||
|
|
||||
|
private void toggleAbbreviation() { |
||||
|
if (number == Long.MIN_VALUE) return; |
||||
|
setShowAbbreviation(!showAbbreviation); |
||||
|
} |
||||
|
|
||||
|
public void setToggleOnClick(final boolean toggleOnClick) { |
||||
|
this.toggleOnClick = toggleOnClick; |
||||
|
} |
||||
|
|
||||
|
public boolean isToggleOnClick() { |
||||
|
return toggleOnClick; |
||||
|
} |
||||
|
|
||||
|
public void setAutoToggleToAbbreviation(final boolean autoToggleToAbbreviation) { |
||||
|
this.autoToggleToAbbreviation = autoToggleToAbbreviation; |
||||
|
} |
||||
|
|
||||
|
public boolean isAutoToggleToAbbreviation() { |
||||
|
return autoToggleToAbbreviation; |
||||
|
} |
||||
|
|
||||
|
public void setAutoToggleTimeoutMs(final long autoToggleTimeoutMs) { |
||||
|
this.autoToggleTimeoutMs = autoToggleTimeoutMs; |
||||
|
} |
||||
|
|
||||
|
public long getAutoToggleTimeoutMs() { |
||||
|
return autoToggleTimeoutMs; |
||||
|
} |
||||
|
|
||||
|
public void setAnimateChanges(final boolean animateChanges) { |
||||
|
this.animateChanges = animateChanges; |
||||
|
} |
||||
|
|
||||
|
public boolean isAnimateChanges() { |
||||
|
return animateChanges; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void setOnClickListener(@Nullable final OnClickListener l) { |
||||
|
super.setOnClickListener(getWrappedClickListener(l)); |
||||
|
} |
||||
|
|
||||
|
private void format() { |
||||
|
post(() -> { |
||||
|
if (animateChanges) { |
||||
|
try { |
||||
|
TransitionManager.beginDelayedTransition((ViewGroup) getParent(), TRANSITION); |
||||
|
} catch (Exception e) { |
||||
|
Log.e(TAG, "format: ", e); |
||||
|
} |
||||
|
} |
||||
|
if (number == Long.MIN_VALUE) { |
||||
|
setText(null); |
||||
|
return; |
||||
|
} |
||||
|
if (showAbbreviation) { |
||||
|
setText(NumberUtils.abbreviate(number)); |
||||
|
return; |
||||
|
} |
||||
|
setText(String.valueOf(number)); |
||||
|
if (autoToggleToAbbreviation) { |
||||
|
getHandler().postDelayed(() -> setShowAbbreviation(true), autoToggleTimeoutMs); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
} |
@ -0,0 +1,320 @@ |
|||||
|
package awais.instagrabber.customviews.helpers; |
||||
|
|
||||
|
/* |
||||
|
* Copyright (C) 2013 The Android Open Source Project |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
|
||||
|
import android.animation.Animator; |
||||
|
import android.animation.AnimatorListenerAdapter; |
||||
|
import android.animation.AnimatorSet; |
||||
|
import android.animation.ValueAnimator; |
||||
|
import android.graphics.Color; |
||||
|
import android.util.Log; |
||||
|
import android.view.ViewGroup; |
||||
|
import android.widget.EditText; |
||||
|
import android.widget.TextView; |
||||
|
|
||||
|
import androidx.annotation.NonNull; |
||||
|
import androidx.transition.Transition; |
||||
|
import androidx.transition.TransitionListenerAdapter; |
||||
|
import androidx.transition.TransitionValues; |
||||
|
|
||||
|
import java.util.Map; |
||||
|
import java.util.Objects; |
||||
|
|
||||
|
import awais.instagrabber.BuildConfig; |
||||
|
|
||||
|
/** |
||||
|
* This transition tracks changes to the text in TextView targets. If the text |
||||
|
* changes between the start and end scenes, the transition ensures that the |
||||
|
* starting text stays until the transition ends, at which point it changes |
||||
|
* to the end text. This is useful in situations where you want to resize a |
||||
|
* text view to its new size before displaying the text that goes there. |
||||
|
*/ |
||||
|
public class ChangeText extends Transition { |
||||
|
private static final String LOG_TAG = "TextChange"; |
||||
|
private static final String PROPNAME_TEXT = "android:textchange:text"; |
||||
|
private static final String PROPNAME_TEXT_SELECTION_START = |
||||
|
"android:textchange:textSelectionStart"; |
||||
|
private static final String PROPNAME_TEXT_SELECTION_END = |
||||
|
"android:textchange:textSelectionEnd"; |
||||
|
private static final String PROPNAME_TEXT_COLOR = "android:textchange:textColor"; |
||||
|
private int mChangeBehavior = CHANGE_BEHAVIOR_KEEP; |
||||
|
private boolean crossFade; |
||||
|
/** |
||||
|
* Flag specifying that the text in affected/changing TextView targets will keep |
||||
|
* their original text during the transition, setting it to the final text when |
||||
|
* the transition ends. This is the default behavior. |
||||
|
* |
||||
|
* @see #setChangeBehavior(int) |
||||
|
*/ |
||||
|
public static final int CHANGE_BEHAVIOR_KEEP = 0; |
||||
|
/** |
||||
|
* Flag specifying that the text changing animation should first fade |
||||
|
* out the original text completely. The new text is set on the target |
||||
|
* view at the end of the fade-out animation. This transition is typically |
||||
|
* used with a later {@link #CHANGE_BEHAVIOR_IN} transition, allowing more |
||||
|
* flexibility than the {@link #CHANGE_BEHAVIOR_OUT_IN} by allowing other |
||||
|
* transitions to be run sequentially or in parallel with these fades. |
||||
|
* |
||||
|
* @see #setChangeBehavior(int) |
||||
|
*/ |
||||
|
public static final int CHANGE_BEHAVIOR_OUT = 1; |
||||
|
/** |
||||
|
* Flag specifying that the text changing animation should fade in the |
||||
|
* end text into the affected target view(s). This transition is typically |
||||
|
* used in conjunction with an earlier {@link #CHANGE_BEHAVIOR_OUT} |
||||
|
* transition, possibly with other transitions running as well, such as |
||||
|
* a sequence to fade out, then resize the view, then fade in. |
||||
|
* |
||||
|
* @see #setChangeBehavior(int) |
||||
|
*/ |
||||
|
public static final int CHANGE_BEHAVIOR_IN = 2; |
||||
|
/** |
||||
|
* Flag specifying that the text changing animation should first fade |
||||
|
* out the original text completely and then fade in the |
||||
|
* new text. |
||||
|
* |
||||
|
* @see #setChangeBehavior(int) |
||||
|
*/ |
||||
|
public static final int CHANGE_BEHAVIOR_OUT_IN = 3; |
||||
|
private static final String[] sTransitionProperties = { |
||||
|
PROPNAME_TEXT, |
||||
|
PROPNAME_TEXT_SELECTION_START, |
||||
|
PROPNAME_TEXT_SELECTION_END |
||||
|
}; |
||||
|
|
||||
|
/** |
||||
|
* Sets the type of changing animation that will be run, one of |
||||
|
* {@link #CHANGE_BEHAVIOR_KEEP}, {@link #CHANGE_BEHAVIOR_OUT}, |
||||
|
* {@link #CHANGE_BEHAVIOR_IN}, and {@link #CHANGE_BEHAVIOR_OUT_IN}. |
||||
|
* |
||||
|
* @param changeBehavior The type of fading animation to use when this |
||||
|
* transition is run. |
||||
|
* @return this textChange object. |
||||
|
*/ |
||||
|
public ChangeText setChangeBehavior(int changeBehavior) { |
||||
|
if (changeBehavior >= CHANGE_BEHAVIOR_KEEP && changeBehavior <= CHANGE_BEHAVIOR_OUT_IN) { |
||||
|
mChangeBehavior = changeBehavior; |
||||
|
} |
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
public ChangeText setCrossFade(final boolean crossFade) { |
||||
|
this.crossFade = crossFade; |
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public String[] getTransitionProperties() { |
||||
|
return sTransitionProperties; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Returns the type of changing animation that will be run. |
||||
|
* |
||||
|
* @return either {@link #CHANGE_BEHAVIOR_KEEP}, {@link #CHANGE_BEHAVIOR_OUT}, |
||||
|
* {@link #CHANGE_BEHAVIOR_IN}, or {@link #CHANGE_BEHAVIOR_OUT_IN}. |
||||
|
*/ |
||||
|
public int getChangeBehavior() { |
||||
|
return mChangeBehavior; |
||||
|
} |
||||
|
|
||||
|
private void captureValues(TransitionValues transitionValues) { |
||||
|
if (transitionValues.view instanceof TextView) { |
||||
|
TextView textview = (TextView) transitionValues.view; |
||||
|
transitionValues.values.put(PROPNAME_TEXT, textview.getText()); |
||||
|
if (textview instanceof EditText) { |
||||
|
transitionValues.values.put(PROPNAME_TEXT_SELECTION_START, |
||||
|
textview.getSelectionStart()); |
||||
|
transitionValues.values.put(PROPNAME_TEXT_SELECTION_END, |
||||
|
textview.getSelectionEnd()); |
||||
|
} |
||||
|
if (mChangeBehavior > CHANGE_BEHAVIOR_KEEP) { |
||||
|
transitionValues.values.put(PROPNAME_TEXT_COLOR, textview.getCurrentTextColor()); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void captureStartValues(@NonNull TransitionValues transitionValues) { |
||||
|
captureValues(transitionValues); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void captureEndValues(@NonNull TransitionValues transitionValues) { |
||||
|
captureValues(transitionValues); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public Animator createAnimator(@NonNull ViewGroup sceneRoot, TransitionValues startValues, |
||||
|
TransitionValues endValues) { |
||||
|
if (startValues == null || endValues == null || |
||||
|
!(startValues.view instanceof TextView) || !(endValues.view instanceof TextView)) { |
||||
|
return null; |
||||
|
} |
||||
|
final TextView view = (TextView) endValues.view; |
||||
|
Map<String, Object> startVals = startValues.values; |
||||
|
Map<String, Object> endVals = endValues.values; |
||||
|
final CharSequence startText = startVals.get(PROPNAME_TEXT) != null ? |
||||
|
(CharSequence) startVals.get(PROPNAME_TEXT) : ""; |
||||
|
final CharSequence endText = endVals.get(PROPNAME_TEXT) != null ? |
||||
|
(CharSequence) endVals.get(PROPNAME_TEXT) : ""; |
||||
|
final int startSelectionStart, startSelectionEnd, endSelectionStart, endSelectionEnd; |
||||
|
if (view instanceof EditText) { |
||||
|
startSelectionStart = startVals.get(PROPNAME_TEXT_SELECTION_START) != null ? |
||||
|
(Integer) startVals.get(PROPNAME_TEXT_SELECTION_START) : -1; |
||||
|
startSelectionEnd = startVals.get(PROPNAME_TEXT_SELECTION_END) != null ? |
||||
|
(Integer) startVals.get(PROPNAME_TEXT_SELECTION_END) : startSelectionStart; |
||||
|
endSelectionStart = endVals.get(PROPNAME_TEXT_SELECTION_START) != null ? |
||||
|
(Integer) endVals.get(PROPNAME_TEXT_SELECTION_START) : -1; |
||||
|
endSelectionEnd = endVals.get(PROPNAME_TEXT_SELECTION_END) != null ? |
||||
|
(Integer) endVals.get(PROPNAME_TEXT_SELECTION_END) : endSelectionStart; |
||||
|
} else { |
||||
|
startSelectionStart = startSelectionEnd = endSelectionStart = endSelectionEnd = -1; |
||||
|
} |
||||
|
if (!Objects.equals(startText, endText)) { |
||||
|
final int startColor; |
||||
|
final int endColor; |
||||
|
if (mChangeBehavior != CHANGE_BEHAVIOR_IN) { |
||||
|
view.setText(startText); |
||||
|
if (view instanceof EditText) { |
||||
|
setSelection(((EditText) view), startSelectionStart, startSelectionEnd); |
||||
|
} |
||||
|
} |
||||
|
Animator anim; |
||||
|
if (mChangeBehavior == CHANGE_BEHAVIOR_KEEP) { |
||||
|
startColor = endColor = 0; |
||||
|
anim = ValueAnimator.ofFloat(0, 1); |
||||
|
anim.addListener(new AnimatorListenerAdapter() { |
||||
|
@Override |
||||
|
public void onAnimationEnd(Animator animation) { |
||||
|
if (Objects.equals(startText, view.getText())) { |
||||
|
// Only set if it hasn't been changed since anim started |
||||
|
view.setText(endText); |
||||
|
if (view instanceof EditText) { |
||||
|
setSelection(((EditText) view), endSelectionStart, endSelectionEnd); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
} else { |
||||
|
startColor = (Integer) startVals.get(PROPNAME_TEXT_COLOR); |
||||
|
endColor = (Integer) endVals.get(PROPNAME_TEXT_COLOR); |
||||
|
// Fade out start text |
||||
|
ValueAnimator outAnim = null, inAnim = null; |
||||
|
if (mChangeBehavior == CHANGE_BEHAVIOR_OUT_IN || |
||||
|
mChangeBehavior == CHANGE_BEHAVIOR_OUT) { |
||||
|
outAnim = ValueAnimator.ofInt(Color.alpha(startColor), 0); |
||||
|
outAnim.addUpdateListener(animation -> { |
||||
|
int currAlpha = (Integer) animation.getAnimatedValue(); |
||||
|
view.setTextColor(currAlpha << 24 | startColor & 0xffffff); |
||||
|
}); |
||||
|
outAnim.addListener(new AnimatorListenerAdapter() { |
||||
|
@Override |
||||
|
public void onAnimationEnd(Animator animation) { |
||||
|
if (Objects.equals(startText, view.getText())) { |
||||
|
// Only set if it hasn't been changed since anim started |
||||
|
view.setText(endText); |
||||
|
if (view instanceof EditText) { |
||||
|
setSelection(((EditText) view), endSelectionStart, |
||||
|
endSelectionEnd); |
||||
|
} |
||||
|
} |
||||
|
// restore opaque alpha and correct end color |
||||
|
view.setTextColor(endColor); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
if (mChangeBehavior == CHANGE_BEHAVIOR_OUT_IN || |
||||
|
mChangeBehavior == CHANGE_BEHAVIOR_IN) { |
||||
|
inAnim = ValueAnimator.ofInt(0, Color.alpha(endColor)); |
||||
|
inAnim.addUpdateListener(animation -> { |
||||
|
int currAlpha = (Integer) animation.getAnimatedValue(); |
||||
|
view.setTextColor(currAlpha << 24 | endColor & 0xffffff); |
||||
|
}); |
||||
|
inAnim.addListener(new AnimatorListenerAdapter() { |
||||
|
@Override |
||||
|
public void onAnimationCancel(Animator animation) { |
||||
|
// restore opaque alpha and correct end color |
||||
|
view.setTextColor(endColor); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
if (outAnim != null && inAnim != null) { |
||||
|
anim = new AnimatorSet(); |
||||
|
final AnimatorSet animatorSet = (AnimatorSet) anim; |
||||
|
if (crossFade) { |
||||
|
animatorSet.playTogether(outAnim, inAnim); |
||||
|
} else { |
||||
|
animatorSet.playSequentially(outAnim, inAnim); |
||||
|
} |
||||
|
} else if (outAnim != null) { |
||||
|
anim = outAnim; |
||||
|
} else { |
||||
|
// Must be an in-only animation |
||||
|
anim = inAnim; |
||||
|
} |
||||
|
} |
||||
|
TransitionListener transitionListener = new TransitionListenerAdapter() { |
||||
|
int mPausedColor = 0; |
||||
|
|
||||
|
@Override |
||||
|
public void onTransitionPause(@NonNull Transition transition) { |
||||
|
if (mChangeBehavior != CHANGE_BEHAVIOR_IN) { |
||||
|
view.setText(endText); |
||||
|
if (view instanceof EditText) { |
||||
|
setSelection(((EditText) view), endSelectionStart, endSelectionEnd); |
||||
|
} |
||||
|
} |
||||
|
if (mChangeBehavior > CHANGE_BEHAVIOR_KEEP) { |
||||
|
mPausedColor = view.getCurrentTextColor(); |
||||
|
view.setTextColor(endColor); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void onTransitionResume(@NonNull Transition transition) { |
||||
|
if (mChangeBehavior != CHANGE_BEHAVIOR_IN) { |
||||
|
view.setText(startText); |
||||
|
if (view instanceof EditText) { |
||||
|
setSelection(((EditText) view), startSelectionStart, startSelectionEnd); |
||||
|
} |
||||
|
} |
||||
|
if (mChangeBehavior > CHANGE_BEHAVIOR_KEEP) { |
||||
|
view.setTextColor(mPausedColor); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void onTransitionEnd(Transition transition) { |
||||
|
transition.removeListener(this); |
||||
|
} |
||||
|
}; |
||||
|
addListener(transitionListener); |
||||
|
if (BuildConfig.DEBUG) { |
||||
|
Log.d(LOG_TAG, "createAnimator returning " + anim); |
||||
|
} |
||||
|
return anim; |
||||
|
} |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
private void setSelection(EditText editText, int start, int end) { |
||||
|
if (start >= 0 && end >= 0) { |
||||
|
editText.setSelection(start, end); |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,91 @@ |
|||||
|
package awais.instagrabber.utils; |
||||
|
|
||||
|
/* |
||||
|
* Copyright (C) 2009 The Android Open Source Project |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
|
||||
|
|
||||
|
import androidx.annotation.NonNull; |
||||
|
import androidx.annotation.Nullable; |
||||
|
import androidx.core.util.ObjectsCompat; |
||||
|
|
||||
|
/** |
||||
|
* Container to ease passing around a tuple of two objects. This object provides a sensible |
||||
|
* implementation of equals(), returning true if equals() is true on each of the contained |
||||
|
* objects. |
||||
|
*/ |
||||
|
public class NullSafePair<F, S> { |
||||
|
public final @NonNull |
||||
|
F first; |
||||
|
public final @NonNull |
||||
|
S second; |
||||
|
|
||||
|
/** |
||||
|
* Constructor for a Pair. |
||||
|
* |
||||
|
* @param first the first object in the Pair |
||||
|
* @param second the second object in the pair |
||||
|
*/ |
||||
|
public NullSafePair(@NonNull F first, @NonNull S second) { |
||||
|
this.first = first; |
||||
|
this.second = second; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Checks the two objects for equality by delegating to their respective |
||||
|
* {@link Object#equals(Object)} methods. |
||||
|
* |
||||
|
* @param o the {@link androidx.core.util.Pair} to which this one is to be checked for equality |
||||
|
* @return true if the underlying objects of the Pair are both considered |
||||
|
* equal |
||||
|
*/ |
||||
|
@Override |
||||
|
public boolean equals(Object o) { |
||||
|
if (!(o instanceof androidx.core.util.Pair)) { |
||||
|
return false; |
||||
|
} |
||||
|
androidx.core.util.Pair<?, ?> p = (androidx.core.util.Pair<?, ?>) o; |
||||
|
return ObjectsCompat.equals(p.first, first) && ObjectsCompat.equals(p.second, second); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Compute a hash code using the hash codes of the underlying objects |
||||
|
* |
||||
|
* @return a hashcode of the Pair |
||||
|
*/ |
||||
|
@Override |
||||
|
public int hashCode() { |
||||
|
return first.hashCode() ^ second.hashCode(); |
||||
|
} |
||||
|
|
||||
|
@NonNull |
||||
|
@Override |
||||
|
public String toString() { |
||||
|
return "Pair{" + first + " " + second + "}"; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Convenience method for creating an appropriately typed pair. |
||||
|
* |
||||
|
* @param a the first object in the Pair |
||||
|
* @param b the second object in the pair |
||||
|
* @return a Pair that is templatized with the types of a and b |
||||
|
*/ |
||||
|
@NonNull |
||||
|
public static <A, B> androidx.core.util.Pair<A, B> create(@Nullable A a, @Nullable B b) { |
||||
|
return new androidx.core.util.Pair<A, B>(a, b); |
||||
|
} |
||||
|
} |
||||
|
|
@ -1,10 +1,10 @@ |
|||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" |
<vector xmlns:android="http://schemas.android.com/apk/res/android" |
||||
android:width="24dp" |
android:width="24dp" |
||||
android:height="24dp" |
android:height="24dp" |
||||
|
android:tint="?colorControlNormal" |
||||
android:viewportWidth="24" |
android:viewportWidth="24" |
||||
android:viewportHeight="24" |
|
||||
android:tint="#333333"> |
|
||||
<path |
|
||||
android:fillColor="@android:color/white" |
|
||||
android:pathData="M17,3H7c-1.1,0 -1.99,0.9 -1.99,2L5,21l7,-3 7,3V5c0,-1.1 -0.9,-2 -2,-2z"/> |
|
||||
|
android:viewportHeight="24"> |
||||
|
<path |
||||
|
android:fillColor="@android:color/white" |
||||
|
android:pathData="M17,3H7c-1.1,0 -1.99,0.9 -1.99,2L5,21l7,-3 7,3V5c0,-1.1 -0.9,-2 -2,-2z" /> |
||||
</vector> |
</vector> |
@ -0,0 +1,10 @@ |
|||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" |
||||
|
android:width="24dp" |
||||
|
android:height="24dp" |
||||
|
android:viewportWidth="24" |
||||
|
android:viewportHeight="24" |
||||
|
android:tint="?attr/colorControlNormal"> |
||||
|
<path |
||||
|
android:fillColor="@android:color/white" |
||||
|
android:pathData="M17,3L7,3c-1.1,0 -2,0.9 -2,2v16l7,-3 7,3L19,5c0,-1.1 -0.9,-2 -2,-2zM17,18l-5,-2.18L7,18L7,6c0,-0.55 0.45,-1 1,-1h8c0.55,0 1,0.45 1,1v12z"/> |
||||
|
</vector> |
@ -0,0 +1,203 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<merge xmlns:android="http://schemas.android.com/apk/res/android" |
||||
|
xmlns:app="http://schemas.android.com/apk/res-auto" |
||||
|
xmlns:tools="http://schemas.android.com/tools" |
||||
|
tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout"> |
||||
|
|
||||
|
<androidx.constraintlayout.widget.Barrier |
||||
|
android:id="@+id/buttons_top_barrier" |
||||
|
android:layout_width="wrap_content" |
||||
|
android:layout_height="wrap_content" |
||||
|
app:barrierAllowsGoneWidgets="true" |
||||
|
app:barrierDirection="bottom" /> |
||||
|
|
||||
|
<com.google.android.material.button.MaterialButton |
||||
|
android:id="@+id/like" |
||||
|
style="@style/Widget.MaterialComponents.Button.TextButton" |
||||
|
android:layout_width="36dp" |
||||
|
android:layout_height="40dp" |
||||
|
android:visibility="visible" |
||||
|
app:icon="@drawable/ic_not_liked" |
||||
|
app:iconGravity="textStart" |
||||
|
app:iconPadding="0dp" |
||||
|
app:iconSize="22dp" |
||||
|
app:layout_constraintBottom_toTopOf="@id/buttons_bottom_barrier" |
||||
|
app:layout_constraintEnd_toStartOf="@id/likes_count" |
||||
|
app:layout_constraintStart_toStartOf="parent" |
||||
|
app:layout_constraintTop_toBottomOf="@id/buttons_top_barrier" |
||||
|
app:layout_constraintVertical_bias="0" |
||||
|
app:layout_constraintVertical_chainStyle="packed" |
||||
|
tools:visibility="visible" /> |
||||
|
|
||||
|
<awais.instagrabber.customviews.FormattedNumberTextView |
||||
|
android:id="@+id/likes_count" |
||||
|
android:layout_width="wrap_content" |
||||
|
android:layout_height="0dp" |
||||
|
android:layout_marginEnd="0dp" |
||||
|
android:background="@drawable/background_grey_ripple" |
||||
|
android:gravity="center_vertical" |
||||
|
android:maxWidth="100dp" |
||||
|
android:textAppearance="@style/TextAppearance.MaterialComponents.Body2" |
||||
|
android:textColor="?android:attr/textColorSecondary" |
||||
|
app:layout_constraintBottom_toBottomOf="@id/like" |
||||
|
app:layout_constraintEnd_toStartOf="@id/comment" |
||||
|
app:layout_constraintStart_toEndOf="@id/like" |
||||
|
app:layout_constraintTop_toTopOf="@id/like" |
||||
|
tools:text="9.9k" /> |
||||
|
|
||||
|
<com.google.android.material.button.MaterialButton |
||||
|
android:id="@+id/comment" |
||||
|
style="@style/Widget.MaterialComponents.Button.TextButton" |
||||
|
android:layout_width="36dp" |
||||
|
android:layout_height="40dp" |
||||
|
android:layout_marginStart="8dp" |
||||
|
app:icon="@drawable/ic_outline_comments_24" |
||||
|
app:iconPadding="0dp" |
||||
|
app:iconSize="22dp" |
||||
|
app:layout_constraintBottom_toTopOf="@id/buttons_bottom_barrier" |
||||
|
app:layout_constraintEnd_toStartOf="@id/comments_count" |
||||
|
app:layout_constraintStart_toEndOf="@id/likes_count" |
||||
|
app:layout_constraintTop_toBottomOf="@id/buttons_top_barrier" /> |
||||
|
|
||||
|
<awais.instagrabber.customviews.FormattedNumberTextView |
||||
|
android:id="@+id/comments_count" |
||||
|
android:layout_width="wrap_content" |
||||
|
android:layout_height="0dp" |
||||
|
android:background="@drawable/background_grey_ripple" |
||||
|
android:ellipsize="end" |
||||
|
android:gravity="center_vertical" |
||||
|
android:maxWidth="100dp" |
||||
|
android:maxLines="1" |
||||
|
android:textAppearance="@style/TextAppearance.MaterialComponents.Body2" |
||||
|
android:textColor="?android:attr/textColorSecondary" |
||||
|
app:layout_constraintBottom_toTopOf="@id/buttons_bottom_barrier" |
||||
|
app:layout_constraintEnd_toStartOf="@id/space_1" |
||||
|
app:layout_constraintStart_toEndOf="@id/comment" |
||||
|
app:layout_constraintTop_toBottomOf="@id/buttons_top_barrier" |
||||
|
tools:text="9999999999999999999999999999999999999999" /> |
||||
|
|
||||
|
<Space |
||||
|
android:id="@+id/space_1" |
||||
|
android:layout_width="0dp" |
||||
|
android:layout_height="0dp" |
||||
|
app:layout_constraintBottom_toTopOf="@id/buttons_bottom_barrier" |
||||
|
app:layout_constraintEnd_toStartOf="@id/share" |
||||
|
app:layout_constraintStart_toEndOf="@id/comments_count" |
||||
|
app:layout_constraintTop_toBottomOf="@id/buttons_top_barrier" /> |
||||
|
|
||||
|
<com.google.android.material.button.MaterialButton |
||||
|
android:id="@+id/share" |
||||
|
style="@style/Widget.MaterialComponents.Button.TextButton" |
||||
|
android:layout_width="48dp" |
||||
|
android:layout_height="40dp" |
||||
|
app:icon="?attr/actionModeShareDrawable" |
||||
|
app:iconGravity="textStart" |
||||
|
app:iconPadding="0dp" |
||||
|
app:iconSize="18dp" |
||||
|
app:layout_constraintBottom_toTopOf="@id/buttons_bottom_barrier" |
||||
|
app:layout_constraintEnd_toStartOf="@id/save" |
||||
|
app:layout_constraintStart_toEndOf="@id/space_1" |
||||
|
app:layout_constraintTop_toBottomOf="@id/buttons_top_barrier" /> |
||||
|
|
||||
|
<com.google.android.material.button.MaterialButton |
||||
|
android:id="@+id/save" |
||||
|
style="@style/Widget.MaterialComponents.Button.TextButton" |
||||
|
android:layout_width="48dp" |
||||
|
android:layout_height="40dp" |
||||
|
android:visibility="visible" |
||||
|
app:icon="@drawable/ic_round_bookmark_border_24" |
||||
|
app:iconGravity="textStart" |
||||
|
app:iconPadding="0dp" |
||||
|
app:iconSize="18dp" |
||||
|
app:layout_constraintBottom_toTopOf="@id/buttons_bottom_barrier" |
||||
|
app:layout_constraintEnd_toStartOf="@id/download" |
||||
|
app:layout_constraintStart_toEndOf="@id/share" |
||||
|
app:layout_constraintTop_toBottomOf="@id/buttons_top_barrier" |
||||
|
tools:visibility="visible" /> |
||||
|
|
||||
|
<com.google.android.material.button.MaterialButton |
||||
|
android:id="@+id/download" |
||||
|
style="@style/Widget.MaterialComponents.Button.TextButton" |
||||
|
android:layout_width="48dp" |
||||
|
android:layout_height="40dp" |
||||
|
android:visibility="visible" |
||||
|
app:icon="@drawable/ic_download" |
||||
|
app:iconGravity="textStart" |
||||
|
app:iconPadding="0dp" |
||||
|
app:iconSize="18dp" |
||||
|
app:layout_constraintBottom_toTopOf="@id/buttons_bottom_barrier" |
||||
|
app:layout_constraintEnd_toEndOf="parent" |
||||
|
app:layout_constraintStart_toEndOf="@id/save" |
||||
|
app:layout_constraintTop_toBottomOf="@id/buttons_top_barrier" |
||||
|
tools:visibility="visible" /> |
||||
|
|
||||
|
<androidx.constraintlayout.widget.Barrier |
||||
|
android:id="@+id/buttons_bottom_barrier" |
||||
|
android:layout_width="0dp" |
||||
|
android:layout_height="0dp" |
||||
|
app:barrierAllowsGoneWidgets="true" |
||||
|
app:barrierDirection="top" /> |
||||
|
|
||||
|
<androidx.appcompat.widget.AppCompatTextView |
||||
|
android:id="@+id/date" |
||||
|
android:layout_width="0dp" |
||||
|
android:layout_height="wrap_content" |
||||
|
android:padding="8dp" |
||||
|
app:layout_constraintBottom_toTopOf="@id/caption_barrier" |
||||
|
app:layout_constraintEnd_toStartOf="@id/views_count" |
||||
|
app:layout_constraintStart_toStartOf="parent" |
||||
|
app:layout_constraintTop_toBottomOf="@id/buttons_bottom_barrier" |
||||
|
tools:layout_constraintVertical_chainStyle="packed" |
||||
|
tools:text="2020-11-07 11:18:55" |
||||
|
tools:visibility="visible" /> |
||||
|
|
||||
|
<androidx.appcompat.widget.AppCompatTextView |
||||
|
android:id="@+id/views_count" |
||||
|
android:layout_width="wrap_content" |
||||
|
android:layout_height="wrap_content" |
||||
|
android:padding="8dp" |
||||
|
app:layout_constraintBottom_toBottomOf="@id/date" |
||||
|
app:layout_constraintEnd_toEndOf="parent" |
||||
|
app:layout_constraintStart_toEndOf="@id/date" |
||||
|
app:layout_constraintTop_toTopOf="@id/date" |
||||
|
tools:text="9999999999 views" |
||||
|
tools:visibility="visible" /> |
||||
|
|
||||
|
<androidx.constraintlayout.widget.Barrier |
||||
|
android:id="@+id/caption_barrier" |
||||
|
android:layout_width="0dp" |
||||
|
android:layout_height="0dp" |
||||
|
app:barrierAllowsGoneWidgets="true" |
||||
|
app:barrierDirection="bottom" /> |
||||
|
|
||||
|
<awais.instagrabber.customviews.RamboTextViewV2 |
||||
|
android:id="@+id/caption" |
||||
|
android:layout_width="match_parent" |
||||
|
android:layout_height="wrap_content" |
||||
|
android:layout_gravity="bottom" |
||||
|
android:background="@null" |
||||
|
android:clickable="true" |
||||
|
android:focusable="true" |
||||
|
android:padding="8dp" |
||||
|
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1" |
||||
|
app:layout_constraintBottom_toTopOf="@id/translate" |
||||
|
app:layout_constraintTop_toBottomOf="@id/caption_barrier" |
||||
|
app:layout_constraintVertical_bias="0" |
||||
|
app:layout_constraintVertical_chainStyle="packed" |
||||
|
tools:text="Text text text Text text text Text text text Text text text Text text text" |
||||
|
tools:visibility="visible" /> |
||||
|
|
||||
|
<androidx.appcompat.widget.AppCompatTextView |
||||
|
android:id="@+id/translate" |
||||
|
android:layout_width="match_parent" |
||||
|
android:layout_height="wrap_content" |
||||
|
android:background="@null" |
||||
|
android:gravity="center_vertical" |
||||
|
android:padding="8dp" |
||||
|
android:text="@string/translate_caption" |
||||
|
android:textColor="@color/blue_600" |
||||
|
android:textSize="16sp" |
||||
|
android:visibility="visible" |
||||
|
app:layout_constraintBottom_toBottomOf="parent" |
||||
|
app:layout_constraintTop_toBottomOf="@id/caption" /> |
||||
|
</merge> |
@ -1,5 +1,5 @@ |
|||||
<?xml version="1.0" encoding="utf-8"?> |
<?xml version="1.0" encoding="utf-8"?> |
||||
<resources> |
<resources> |
||||
<drawable name="exo_styled_controls_play">@drawable/ic_play_arrow_24</drawable> |
|
||||
<drawable name="exo_styled_controls_pause">@drawable/ic_pause_24</drawable> |
|
||||
|
<!--<drawable name="exo_styled_controls_play">@drawable/ic_play_arrow_24</drawable>--> |
||||
|
<!--<drawable name="exo_styled_controls_pause">@drawable/ic_pause_24</drawable>--> |
||||
</resources> |
</resources> |
Write
Preview
Loading…
Cancel
Save
Reference in new issue