diff --git a/app/src/main/java/awais/instagrabber/activities/MainActivity.java b/app/src/main/java/awais/instagrabber/activities/MainActivity.java index 36e02a8a..299db16b 100644 --- a/app/src/main/java/awais/instagrabber/activities/MainActivity.java +++ b/app/src/main/java/awais/instagrabber/activities/MainActivity.java @@ -504,11 +504,12 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage @SuppressLint("RestrictedApi") final Deque backStack = navController.getBackStack(); setupMenu(backStack.size(), destinationId); final boolean contains = showBottomViewDestinations.contains(destinationId); - binding.bottomNavView.setVisibility(contains ? View.VISIBLE : View.GONE); - if (contains && behavior != null) { - behavior.slideUp(binding.bottomNavView); - } - + binding.getRoot().post(() -> { + binding.bottomNavView.setVisibility(contains ? View.VISIBLE : View.GONE); + if (contains && behavior != null) { + behavior.slideUp(binding.bottomNavView); + } + }); // explicitly hide keyboard when we navigate final View view = getCurrentFocus(); Utils.hideKeyboard(view); diff --git a/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemAnimatedMediaViewHolder.java b/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemAnimatedMediaViewHolder.java index 7bc5d173..5b3b1e33 100644 --- a/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemAnimatedMediaViewHolder.java +++ b/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemAnimatedMediaViewHolder.java @@ -4,7 +4,6 @@ import android.view.View; import android.view.ViewGroup; import androidx.annotation.NonNull; -import androidx.core.util.Pair; import androidx.recyclerview.widget.ItemTouchHelper; import com.facebook.drawee.backends.pipeline.Fresco; @@ -23,6 +22,7 @@ import awais.instagrabber.repositories.responses.User; import awais.instagrabber.repositories.responses.directmessages.DirectItem; import awais.instagrabber.repositories.responses.directmessages.DirectItemAnimatedMedia; import awais.instagrabber.repositories.responses.directmessages.DirectThread; +import awais.instagrabber.utils.NullSafePair; import awais.instagrabber.utils.NumberUtils; import awais.instagrabber.utils.Utils; @@ -48,7 +48,7 @@ public class DirectItemAnimatedMediaViewHolder extends DirectItemViewHolder { final AnimatedMediaFixedHeight fixedHeight = images.getFixedHeight(); if (fixedHeight == null) return; final String url = fixedHeight.getWebp(); - final Pair widthHeight = NumberUtils.calculateWidthHeight( + final NullSafePair widthHeight = NumberUtils.calculateWidthHeight( fixedHeight.getHeight(), fixedHeight.getWidth(), mediaImageMaxHeight, @@ -56,8 +56,8 @@ public class DirectItemAnimatedMediaViewHolder extends DirectItemViewHolder { ); binding.ivAnimatedMessage.setVisibility(View.VISIBLE); final ViewGroup.LayoutParams layoutParams = binding.ivAnimatedMessage.getLayoutParams(); - final int width = widthHeight.first != null ? widthHeight.first : 0; - final int height = widthHeight.second != null ? widthHeight.second : 0; + final int width = widthHeight.first; + final int height = widthHeight.second; layoutParams.width = width; layoutParams.height = height; binding.ivAnimatedMessage.requestLayout(); diff --git a/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemMediaShareViewHolder.java b/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemMediaShareViewHolder.java index dddd9315..82c07ab7 100644 --- a/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemMediaShareViewHolder.java +++ b/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemMediaShareViewHolder.java @@ -6,7 +6,6 @@ import android.view.ViewGroup; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.core.util.Pair; import androidx.recyclerview.widget.ItemTouchHelper; import com.facebook.drawee.drawable.ScalingUtils; @@ -31,6 +30,7 @@ import awais.instagrabber.repositories.responses.directmessages.DirectItem; import awais.instagrabber.repositories.responses.directmessages.DirectItemClip; import awais.instagrabber.repositories.responses.directmessages.DirectItemFelixShare; import awais.instagrabber.repositories.responses.directmessages.DirectThread; +import awais.instagrabber.utils.NullSafePair; import awais.instagrabber.utils.NumberUtils; import awais.instagrabber.utils.ResponseBodyUtils; import awais.instagrabber.utils.Utils; @@ -103,15 +103,15 @@ public class DirectItemMediaShareViewHolder extends DirectItemViewHolder { .setActualImageScaleType(ScalingUtils.ScaleType.CENTER_CROP) .setRoundingParams(roundingParams) .build()); - final Pair widthHeight = NumberUtils.calculateWidthHeight( + final NullSafePair widthHeight = NumberUtils.calculateWidthHeight( media.getOriginalHeight(), media.getOriginalWidth(), mediaImageMaxHeight, mediaImageMaxWidth ); final ViewGroup.LayoutParams layoutParams = binding.mediaPreview.getLayoutParams(); - layoutParams.width = widthHeight.first != null ? widthHeight.first : 0; - layoutParams.height = widthHeight.second != null ? widthHeight.second : 0; + layoutParams.width = widthHeight.first; + layoutParams.height = widthHeight.second; binding.mediaPreview.requestLayout(); binding.mediaPreview.setTag(url); binding.mediaPreview.setImageURI(url); diff --git a/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemMediaViewHolder.java b/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemMediaViewHolder.java index 24a9e62e..769548a8 100644 --- a/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemMediaViewHolder.java +++ b/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemMediaViewHolder.java @@ -4,7 +4,6 @@ import android.view.View; import android.view.ViewGroup; import androidx.annotation.NonNull; -import androidx.core.util.Pair; import com.facebook.drawee.drawable.ScalingUtils; import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder; @@ -14,11 +13,11 @@ import awais.instagrabber.adapters.DirectItemsAdapter.DirectItemCallback; import awais.instagrabber.databinding.LayoutDmBaseBinding; import awais.instagrabber.databinding.LayoutDmMediaBinding; import awais.instagrabber.models.enums.MediaItemType; -import awais.instagrabber.repositories.responses.ImageVersions2; import awais.instagrabber.repositories.responses.Media; import awais.instagrabber.repositories.responses.User; import awais.instagrabber.repositories.responses.directmessages.DirectItem; import awais.instagrabber.repositories.responses.directmessages.DirectThread; +import awais.instagrabber.utils.NullSafePair; import awais.instagrabber.utils.NumberUtils; import awais.instagrabber.utils.ResponseBodyUtils; @@ -53,16 +52,16 @@ public class DirectItemMediaViewHolder extends DirectItemViewHolder { binding.typeIcon.setVisibility(modelMediaType == MediaItemType.MEDIA_TYPE_VIDEO || modelMediaType == MediaItemType.MEDIA_TYPE_SLIDER ? View.VISIBLE : View.GONE); - final Pair widthHeight = NumberUtils.calculateWidthHeight( + final NullSafePair widthHeight = NumberUtils.calculateWidthHeight( media.getOriginalHeight(), media.getOriginalWidth(), mediaImageMaxHeight, mediaImageMaxWidth ); final ViewGroup.LayoutParams layoutParams = binding.mediaPreview.getLayoutParams(); - final int width = widthHeight.first != null ? widthHeight.first : 0; + final int width = widthHeight.first; layoutParams.width = width; - layoutParams.height = widthHeight.second != null ? widthHeight.second : 0; + layoutParams.height = widthHeight.second; binding.mediaPreview.requestLayout(); binding.bgTime.getLayoutParams().width = width; binding.bgTime.requestLayout(); diff --git a/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemRavenMediaViewHolder.java b/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemRavenMediaViewHolder.java index 6198faf1..9778d046 100644 --- a/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemRavenMediaViewHolder.java +++ b/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemRavenMediaViewHolder.java @@ -4,7 +4,6 @@ import android.view.View; import android.view.ViewGroup; import androidx.annotation.NonNull; -import androidx.core.util.Pair; import com.facebook.drawee.drawable.ScalingUtils; import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder; @@ -21,6 +20,7 @@ import awais.instagrabber.repositories.responses.User; import awais.instagrabber.repositories.responses.directmessages.DirectItem; import awais.instagrabber.repositories.responses.directmessages.DirectItemVisualMedia; import awais.instagrabber.repositories.responses.directmessages.DirectThread; +import awais.instagrabber.utils.NullSafePair; import awais.instagrabber.utils.NumberUtils; import awais.instagrabber.utils.ResponseBodyUtils; import awais.instagrabber.utils.TextUtils; @@ -170,15 +170,15 @@ public class DirectItemRavenMediaViewHolder extends DirectItemViewHolder { binding.typeIcon.setVisibility(modelMediaType == MediaItemType.MEDIA_TYPE_VIDEO || modelMediaType == MediaItemType.MEDIA_TYPE_SLIDER ? View.VISIBLE : View.GONE); - final Pair widthHeight = NumberUtils.calculateWidthHeight( + final NullSafePair widthHeight = NumberUtils.calculateWidthHeight( media.getOriginalHeight(), media.getOriginalWidth(), mediaImageMaxHeight, maxWidth ); final ViewGroup.LayoutParams layoutParams = binding.preview.getLayoutParams(); - layoutParams.width = widthHeight.first != null ? widthHeight.first : 0; - layoutParams.height = widthHeight.second != null ? widthHeight.second : 0; + layoutParams.width = widthHeight.first; + layoutParams.height = widthHeight.second; binding.preview.requestLayout(); final String thumbUrl = ResponseBodyUtils.getThumbUrl(media); binding.preview.setImageURI(thumbUrl); diff --git a/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemStoryShareViewHolder.java b/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemStoryShareViewHolder.java index b6b7bf68..45a6a8d4 100644 --- a/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemStoryShareViewHolder.java +++ b/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemStoryShareViewHolder.java @@ -5,7 +5,6 @@ import android.view.View; import android.view.ViewGroup; import androidx.annotation.NonNull; -import androidx.core.util.Pair; import androidx.recyclerview.widget.ItemTouchHelper; import com.facebook.drawee.drawable.ScalingUtils; @@ -17,12 +16,12 @@ import awais.instagrabber.adapters.DirectItemsAdapter.DirectItemCallback; import awais.instagrabber.databinding.LayoutDmBaseBinding; import awais.instagrabber.databinding.LayoutDmStoryShareBinding; import awais.instagrabber.models.enums.MediaItemType; -import awais.instagrabber.repositories.responses.ImageVersions2; import awais.instagrabber.repositories.responses.Media; import awais.instagrabber.repositories.responses.User; import awais.instagrabber.repositories.responses.directmessages.DirectItem; import awais.instagrabber.repositories.responses.directmessages.DirectItemStoryShare; import awais.instagrabber.repositories.responses.directmessages.DirectThread; +import awais.instagrabber.utils.NullSafePair; import awais.instagrabber.utils.NumberUtils; import awais.instagrabber.utils.ResponseBodyUtils; import awais.instagrabber.utils.TextUtils; @@ -76,15 +75,15 @@ public class DirectItemStoryShareViewHolder extends DirectItemViewHolder { .setRoundingParams(roundingParams) .setActualImageScaleType(ScalingUtils.ScaleType.CENTER_CROP) .build()); - final Pair widthHeight = NumberUtils.calculateWidthHeight( + final NullSafePair widthHeight = NumberUtils.calculateWidthHeight( storyShareMedia.getOriginalHeight(), storyShareMedia.getOriginalWidth(), mediaImageMaxHeight, mediaImageMaxWidth ); final ViewGroup.LayoutParams layoutParams = binding.ivMediaPreview.getLayoutParams(); - layoutParams.width = widthHeight.first != null ? widthHeight.first : 0; - layoutParams.height = widthHeight.second != null ? widthHeight.second : 0; + layoutParams.width = widthHeight.first; + layoutParams.height = widthHeight.second; binding.ivMediaPreview.requestLayout(); final String thumbUrl = ResponseBodyUtils.getThumbUrl(storyShareMedia); binding.ivMediaPreview.setImageURI(thumbUrl); diff --git a/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemXmaViewHolder.java b/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemXmaViewHolder.java index 1b1a9fda..adee0226 100644 --- a/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemXmaViewHolder.java +++ b/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemXmaViewHolder.java @@ -4,7 +4,6 @@ import android.view.View; import android.view.ViewGroup; import androidx.annotation.NonNull; -import androidx.core.util.Pair; import androidx.recyclerview.widget.ItemTouchHelper; import com.facebook.drawee.backends.pipeline.Fresco; @@ -16,6 +15,7 @@ import awais.instagrabber.repositories.responses.User; import awais.instagrabber.repositories.responses.directmessages.DirectItem; import awais.instagrabber.repositories.responses.directmessages.DirectItemXma; import awais.instagrabber.repositories.responses.directmessages.DirectThread; +import awais.instagrabber.utils.NullSafePair; import awais.instagrabber.utils.NumberUtils; public class DirectItemXmaViewHolder extends DirectItemViewHolder { @@ -43,7 +43,7 @@ public class DirectItemXmaViewHolder extends DirectItemViewHolder { } final DirectItemXma.XmaUrlInfo urlInfo = playableUrlInfo != null ? playableUrlInfo : previewUrlInfo; final String url = urlInfo.getUrl(); - final Pair widthHeight = NumberUtils.calculateWidthHeight( + final NullSafePair widthHeight = NumberUtils.calculateWidthHeight( urlInfo.getHeight(), urlInfo.getWidth(), mediaImageMaxHeight, @@ -51,8 +51,8 @@ public class DirectItemXmaViewHolder extends DirectItemViewHolder { ); binding.ivAnimatedMessage.setVisibility(View.VISIBLE); final ViewGroup.LayoutParams layoutParams = binding.ivAnimatedMessage.getLayoutParams(); - final int width = widthHeight.first != null ? widthHeight.first : 0; - final int height = widthHeight.second != null ? widthHeight.second : 0; + final int width = widthHeight.first; + final int height = widthHeight.second; layoutParams.width = width; layoutParams.height = height; binding.ivAnimatedMessage.requestLayout(); diff --git a/app/src/main/java/awais/instagrabber/customviews/FormattedNumberTextView.java b/app/src/main/java/awais/instagrabber/customviews/FormattedNumberTextView.java new file mode 100644 index 00000000..77696e17 --- /dev/null +++ b/app/src/main/java/awais/instagrabber/customviews/FormattedNumberTextView.java @@ -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); + } + }); + } + + +} diff --git a/app/src/main/java/awais/instagrabber/customviews/VideoPlayerViewHelper.java b/app/src/main/java/awais/instagrabber/customviews/VideoPlayerViewHelper.java index 56ed8976..84d2305e 100644 --- a/app/src/main/java/awais/instagrabber/customviews/VideoPlayerViewHelper.java +++ b/app/src/main/java/awais/instagrabber/customviews/VideoPlayerViewHelper.java @@ -6,6 +6,7 @@ import android.net.Uri; import android.os.Looper; import android.util.Log; import android.view.View; +import android.view.ViewGroup; import androidx.annotation.NonNull; import androidx.appcompat.widget.PopupMenu; @@ -22,11 +23,13 @@ import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.SimpleExoPlayer; import com.google.android.exoplayer2.audio.AudioListener; import com.google.android.exoplayer2.source.ProgressiveMediaSource; +import com.google.android.exoplayer2.ui.AspectRatioFrameLayout; import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory; import com.google.android.material.button.MaterialButton; import awais.instagrabber.R; import awais.instagrabber.databinding.LayoutVideoPlayerWithThumbnailBinding; +import awais.instagrabber.utils.Utils; public class VideoPlayerViewHelper implements Player.EventListener { private static final String TAG = "VideoPlayerViewHelper"; @@ -149,25 +152,25 @@ public class VideoPlayerViewHelper implements Player.EventListener { if (thumbnailUrl != null) { thumbnailRequest = ImageRequestBuilder.newBuilderWithSource(Uri.parse(thumbnailUrl)).build(); } - final PipelineDraweeControllerBuilder builder = Fresco.newDraweeControllerBuilder() - .setControllerListener(new BaseControllerListener() { - @Override - public void onFailure(final String id, - final Throwable throwable) { - if (videoPlayerCallback != null) { - videoPlayerCallback.onThumbnailLoaded(); - } - } + final PipelineDraweeControllerBuilder builder = Fresco + .newDraweeControllerBuilder() + .setControllerListener(new BaseControllerListener() { + @Override + public void onFailure(final String id, final Throwable throwable) { + if (videoPlayerCallback != null) { + videoPlayerCallback.onThumbnailLoaded(); + } + } - @Override - public void onFinalImageSet(final String id, - final ImageInfo imageInfo, - final Animatable animatable) { - if (videoPlayerCallback != null) { - videoPlayerCallback.onThumbnailLoaded(); - } - } - }); + @Override + public void onFinalImageSet(final String id, + final ImageInfo imageInfo, + final Animatable animatable) { + if (videoPlayerCallback != null) { + videoPlayerCallback.onThumbnailLoaded(); + } + } + }); if (thumbnailRequest != null) { builder.setImageRequest(thumbnailRequest); } @@ -176,8 +179,8 @@ public class VideoPlayerViewHelper implements Player.EventListener { private void loadPlayer() { if (videoUrl == null) return; - if (binding.root.getDisplayedChild() == 0) { - binding.root.showNext(); + if (binding.getRoot().getDisplayedChild() == 0) { + binding.getRoot().showNext(); } if (videoPlayerCallback != null) { videoPlayerCallback.onPlayerViewLoaded(); @@ -186,6 +189,10 @@ public class VideoPlayerViewHelper implements Player.EventListener { if (player != null) { player.release(); } + final ViewGroup.LayoutParams playerViewLayoutParams = binding.playerView.getLayoutParams(); + if (playerViewLayoutParams.height > Utils.displayMetrics.heightPixels * 0.8) { + playerViewLayoutParams.height = (int) (Utils.displayMetrics.heightPixels * 0.8); + } player = new SimpleExoPlayer.Builder(context) .setLooper(Looper.getMainLooper()) .build(); @@ -206,8 +213,11 @@ public class VideoPlayerViewHelper implements Player.EventListener { // setupControls(); player.prepare(); binding.playerView.setPlayer(player); - binding.playerView.setShowFastForwardButton(false); - binding.playerView.setShowRewindButton(false); + // binding.playerView.setShowFastForwardButton(false); + // binding.playerView.setShowRewindButton(false); + binding.playerView.setResizeMode(AspectRatioFrameLayout.RESIZE_MODE_FIXED_HEIGHT); + binding.playerView.setShowNextButton(false); + binding.playerView.setShowPreviousButton(false); // binding.controls.setPlayer(player); mute = binding.playerView.findViewById(R.id.mute); // mute = binding.controls.findViewById(R.id.mute); diff --git a/app/src/main/java/awais/instagrabber/customviews/helpers/ChangeText.java b/app/src/main/java/awais/instagrabber/customviews/helpers/ChangeText.java new file mode 100644 index 00000000..bd3613ec --- /dev/null +++ b/app/src/main/java/awais/instagrabber/customviews/helpers/ChangeText.java @@ -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 startVals = startValues.values; + Map 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); + } + } +} diff --git a/app/src/main/java/awais/instagrabber/fragments/PostViewV2Fragment.java b/app/src/main/java/awais/instagrabber/fragments/PostViewV2Fragment.java index 49768182..cd2d845f 100644 --- a/app/src/main/java/awais/instagrabber/fragments/PostViewV2Fragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/PostViewV2Fragment.java @@ -1,6 +1,5 @@ package awais.instagrabber.fragments; -import android.annotation.SuppressLint; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; @@ -12,10 +11,8 @@ import android.os.Bundle; import android.text.SpannableStringBuilder; import android.text.Spanned; import android.util.Log; -import android.view.GestureDetector; import android.view.LayoutInflater; import android.view.MenuItem; -import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.widget.Toast; @@ -25,9 +22,8 @@ import androidx.annotation.Nullable; import androidx.appcompat.content.res.AppCompatResources; import androidx.appcompat.view.ContextThemeWrapper; import androidx.appcompat.widget.PopupMenu; +import androidx.constraintlayout.widget.ConstraintLayout; import androidx.core.content.PermissionChecker; -import androidx.core.util.Pair; -import androidx.core.widget.NestedScrollView; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentActivity; import androidx.lifecycle.LiveData; @@ -42,10 +38,11 @@ import androidx.transition.TransitionManager; import androidx.viewpager2.widget.ViewPager2; import com.facebook.drawee.backends.pipeline.Fresco; +import com.facebook.drawee.drawable.ScalingUtils; +import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder; import com.facebook.drawee.interfaces.DraweeController; import com.facebook.imagepipeline.request.ImageRequest; import com.facebook.imagepipeline.request.ImageRequestBuilder; -import com.google.android.material.bottomsheet.BottomSheetBehavior; import com.google.android.material.snackbar.BaseTransientBottomBar; import com.google.android.material.snackbar.Snackbar; @@ -60,7 +57,11 @@ import awais.instagrabber.customviews.VerticalImageSpan; import awais.instagrabber.customviews.VideoPlayerCallbackAdapter; import awais.instagrabber.customviews.VideoPlayerViewHelper; import awais.instagrabber.customviews.drawee.AnimatedZoomableController; +import awais.instagrabber.customviews.drawee.DoubleTapGestureListener; +import awais.instagrabber.customviews.drawee.ZoomableDraweeView; import awais.instagrabber.databinding.DialogPostViewBinding; +import awais.instagrabber.databinding.LayoutPostViewBottomBinding; +import awais.instagrabber.databinding.LayoutVideoPlayerWithThumbnailBinding; import awais.instagrabber.dialogs.EditTextDialogFragment; import awais.instagrabber.models.Resource; import awais.instagrabber.models.enums.MediaItemType; @@ -71,6 +72,7 @@ import awais.instagrabber.repositories.responses.User; import awais.instagrabber.repositories.responses.VideoVersion; import awais.instagrabber.utils.Constants; import awais.instagrabber.utils.DownloadUtils; +import awais.instagrabber.utils.NullSafePair; import awais.instagrabber.utils.NumberUtils; import awais.instagrabber.utils.ResponseBodyUtils; import awais.instagrabber.utils.TextUtils; @@ -85,30 +87,26 @@ import static awais.instagrabber.utils.Utils.settingsHelper; public class PostViewV2Fragment extends Fragment implements EditTextDialogFragment.EditTextDialogFragmentCallback { private static final String TAG = "PostViewV2Fragment"; - private static final int DETAILS_HIDE_DELAY_MILLIS = 2000; + // private static final int DETAILS_HIDE_DELAY_MILLIS = 2000; public static final String ARG_MEDIA = "media"; public static final String ARG_SLIDER_POSITION = "position"; private static final int STORAGE_PERM_REQUEST_CODE = 8020; - // private Media media; private DialogPostViewBinding binding; - // private MediaService mediaService; - // private Context context; - private BottomSheetBehavior bottomSheetBehavior; private boolean detailsVisible = true; private boolean video; private VideoPlayerViewHelper videoPlayerViewHelper; private SliderItemsAdapter sliderItemsAdapter; - // private boolean wasControlsVisible; - private int captionState = BottomSheetBehavior.STATE_HIDDEN; private int sliderPosition = -1; - private boolean hasBeenToggled = false; private PostViewV2ViewModel viewModel; private PopupMenu optionsPopup; private EditTextDialogFragment editTextDialogFragment; private boolean wasDeleted; private MutableLiveData backStackSavedStateResultLiveData; private OnDeleteListener onDeleteListener; + @Nullable + private ViewPager2 sliderParent; + private LayoutPostViewBottomBinding bottom; private final Observer backStackSavedStateObserver = result -> { if (result == null) return; @@ -119,13 +117,6 @@ public class PostViewV2Fragment extends Fragment implements EditTextDialogFragme // clear result backStackSavedStateResultLiveData.postValue(null); }; - private final GestureDetector.OnGestureListener videoPlayerViewGestureListener = new GestureDetector.SimpleOnGestureListener() { - @Override - public boolean onSingleTapConfirmed(final MotionEvent e) { - binding.videoPost.playerView.performClick(); - return true; - } - }; public void setOnDeleteListener(final OnDeleteListener onDeleteListener) { if (onDeleteListener == null) return; @@ -143,8 +134,6 @@ public class PostViewV2Fragment extends Fragment implements EditTextDialogFragme public void onCreate(@Nullable final Bundle savedInstanceState) { super.onCreate(savedInstanceState); viewModel = new ViewModelProvider(this).get(PostViewV2ViewModel.class); - captionState = settingsHelper.getBoolean(Constants.SHOW_CAPTIONS) ? - BottomSheetBehavior.STATE_COLLAPSED : BottomSheetBehavior.STATE_HIDDEN; } @Nullable @@ -153,6 +142,7 @@ public class PostViewV2Fragment extends Fragment implements EditTextDialogFragme @Nullable final ViewGroup container, @Nullable final Bundle savedInstanceState) { binding = DialogPostViewBinding.inflate(inflater, container, false); + bottom = LayoutPostViewBottomBinding.bind(binding.getRoot()); return binding.getRoot(); } @@ -166,9 +156,6 @@ public class PostViewV2Fragment extends Fragment implements EditTextDialogFragme public void onPause() { super.onPause(); // wasPaused = true; - if (bottomSheetBehavior != null) { - captionState = bottomSheetBehavior.getState(); - } if (settingsHelper.getBoolean(Constants.PLAY_IN_BACKGROUND)) return; final Media media = viewModel.getMedia(); if (media == null) return; @@ -284,33 +271,35 @@ public class PostViewV2Fragment extends Fragment implements EditTextDialogFragme viewModel.getLocation().observe(getViewLifecycleOwner(), location -> binding.getRoot().post(() -> setupLocation(location))); viewModel.getDate().observe(getViewLifecycleOwner(), date -> binding.getRoot().post(() -> { if (date == null) { - binding.date.setVisibility(View.GONE); + bottom.date.setVisibility(View.GONE); return; } - binding.date.setVisibility(View.VISIBLE); - binding.date.setText(date); + bottom.date.setVisibility(View.VISIBLE); + bottom.date.setText(date); })); viewModel.getLikeCount().observe(getViewLifecycleOwner(), count -> { - final long safeCount = getSafeCount(count); - final String likesString = getResources().getQuantityString(R.plurals.likes_count, (int) safeCount, safeCount); - binding.likesCount.setText(likesString); + bottom.likesCount.setAnimateChanges(false); + bottom.likesCount.setNumber(getSafeCount(count)); + // final String likesString = getResources().getQuantityString(R.plurals.likes_count, (int) safeCount, safeCount); + // bottom.likesCount.setText(likesString); }); if (!viewModel.getMedia().isCommentsDisabled()) { viewModel.getCommentCount().observe(getViewLifecycleOwner(), count -> { - final long safeCount = getSafeCount(count); - final String likesString = getResources().getQuantityString(R.plurals.comments_count, (int) safeCount, safeCount); - binding.commentsCount.setText(likesString); + bottom.commentsCount.setAnimateChanges(false); + bottom.commentsCount.setNumber(getSafeCount(count)); + // final String likesString = getResources().getQuantityString(R.plurals.comments_count, (int) safeCount, safeCount); + // bottom.commentsCount.setText(likesString); }); } viewModel.getViewCount().observe(getViewLifecycleOwner(), count -> { if (count == null) { - binding.viewsCount.setVisibility(View.GONE); + bottom.viewsCount.setVisibility(View.GONE); return; } - binding.viewsCount.setVisibility(View.VISIBLE); + bottom.viewsCount.setVisibility(View.VISIBLE); final long safeCount = getSafeCount(count); final String viewString = getResources().getQuantityString(R.plurals.views_count, (int) safeCount, safeCount); - binding.viewsCount.setText(viewString); + bottom.viewsCount.setText(viewString); }); viewModel.getType().observe(getViewLifecycleOwner(), this::setupPostTypeLayout); viewModel.getLiked().observe(getViewLifecycleOwner(), this::setLikedResources); @@ -340,12 +329,12 @@ public class PostViewV2Fragment extends Fragment implements EditTextDialogFragme private void setupComment() { if (!viewModel.hasPk() || viewModel.getMedia().isCommentsDisabled()) { - binding.comment.setVisibility(View.GONE); - binding.commentsCount.setVisibility(View.GONE); + bottom.comment.setVisibility(View.GONE); + // bottom.commentsCount.setVisibility(View.GONE); return; } - binding.comment.setVisibility(View.VISIBLE); - binding.comment.setOnClickListener(v -> { + bottom.comment.setVisibility(View.VISIBLE); + bottom.comment.setOnClickListener(v -> { final Media media = viewModel.getMedia(); final User user = media.getUser(); if (user == null) return; @@ -361,7 +350,7 @@ public class PostViewV2Fragment extends Fragment implements EditTextDialogFragme Log.e(TAG, "setupComment: ", e); } }); - binding.comment.setOnLongClickListener(v -> { + bottom.comment.setOnLongClickListener(v -> { final Context context = getContext(); if (context == null) return false; Utils.displayToastAboveView(context, v, getString(R.string.comment)); @@ -370,7 +359,7 @@ public class PostViewV2Fragment extends Fragment implements EditTextDialogFragme } private void setupDownload() { - binding.download.setOnClickListener(v -> { + bottom.download.setOnClickListener(v -> { final Context context = getContext(); if (context == null) return; if (checkSelfPermission(context, WRITE_PERMISSION) == PermissionChecker.PERMISSION_GRANTED) { @@ -379,7 +368,7 @@ public class PostViewV2Fragment extends Fragment implements EditTextDialogFragme } requestPermissions(DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE); }); - binding.download.setOnLongClickListener(v -> { + bottom.download.setOnLongClickListener(v -> { final Context context = getContext(); if (context == null) return false; Utils.displayToastAboveView(context, v, getString(R.string.action_download)); @@ -390,19 +379,19 @@ public class PostViewV2Fragment extends Fragment implements EditTextDialogFragme private void setupLike() { final boolean likableMedia = viewModel.hasPk() /*&& viewModel.getMedia().isCommentLikesEnabled()*/; if (!likableMedia) { - binding.like.setVisibility(View.GONE); - binding.likesCount.setVisibility(View.GONE); + bottom.like.setVisibility(View.GONE); + // bottom.likesCount.setVisibility(View.GONE); return; } if (!viewModel.isLoggedIn()) { - binding.like.setVisibility(View.GONE); + bottom.like.setVisibility(View.GONE); return; } - binding.like.setOnClickListener(v -> { + bottom.like.setOnClickListener(v -> { v.setEnabled(false); handleLikeUnlikeResourceLiveData(viewModel.toggleLike()); }); - binding.like.setOnLongClickListener(v -> { + bottom.like.setOnLongClickListener(v -> { final NavController navController = getNavController(); if (navController != null && viewModel.isLoggedIn()) { final Bundle bundle = new Bundle(); @@ -419,14 +408,14 @@ public class PostViewV2Fragment extends Fragment implements EditTextDialogFragme resource.observe(getViewLifecycleOwner(), value -> { switch (value.status) { case SUCCESS: - binding.like.setEnabled(true); + bottom.like.setEnabled(true); break; case ERROR: - binding.like.setEnabled(true); + bottom.like.setEnabled(true); unsuccessfulLike(); break; case LOADING: - binding.like.setEnabled(false); + bottom.like.setEnabled(false); break; } }); @@ -464,20 +453,20 @@ public class PostViewV2Fragment extends Fragment implements EditTextDialogFragme tintResource = getAttrValue(context, R.attr.colorPrimary); // textResId = R.string.like_without_count; } - binding.like.setIconResource(iconResource); - binding.like.setIconTint(ColorStateList.valueOf(tintResource)); + bottom.like.setIconResource(iconResource); + bottom.like.setIconTint(ColorStateList.valueOf(tintResource)); } private void setupSave() { if (!viewModel.isLoggedIn() || !viewModel.hasPk() || !viewModel.getMedia().canViewerSave()) { - binding.save.setVisibility(View.GONE); + bottom.save.setVisibility(View.GONE); return; } - binding.save.setOnClickListener(v -> { - binding.save.setEnabled(false); + bottom.save.setOnClickListener(v -> { + bottom.save.setEnabled(false); handleSaveUnsaveResourceLiveData(viewModel.toggleSave()); }); - binding.save.setOnLongClickListener(v -> { + bottom.save.setOnLongClickListener(v -> { final NavController navController = NavHostFragment.findNavController(this); final Bundle bundle = new Bundle(); bundle.putBoolean("isSaving", true); @@ -491,14 +480,14 @@ public class PostViewV2Fragment extends Fragment implements EditTextDialogFragme if (value == null) return; switch (value.status) { case SUCCESS: - binding.save.setEnabled(true); + bottom.save.setEnabled(true); break; case ERROR: - binding.save.setEnabled(true); + bottom.save.setEnabled(true); unsuccessfulSave(); break; case LOADING: - binding.save.setEnabled(false); + bottom.save.setEnabled(false); break; } }); @@ -527,16 +516,16 @@ public class PostViewV2Fragment extends Fragment implements EditTextDialogFragme final Resources resources = context.getResources(); if (resources == null) return; if (saved) { - iconResource = R.drawable.ic_class_24; + iconResource = R.drawable.ic_bookmark; tintResource = resources.getColor(R.color.blue_700); // textResId = R.string.saved; } else { - iconResource = R.drawable.ic_outline_class_24; + iconResource = R.drawable.ic_round_bookmark_border_24; tintResource = getAttrValue(context, R.attr.colorPrimary); // textResId = R.string.save; } - binding.save.setIconResource(iconResource); - binding.save.setIconTint(ColorStateList.valueOf(tintResource)); + bottom.save.setIconResource(iconResource); + bottom.save.setIconTint(ColorStateList.valueOf(tintResource)); } private void setupProfilePic(final User user) { @@ -598,32 +587,32 @@ public class PostViewV2Fragment extends Fragment implements EditTextDialogFragme private void setupCaption(final Caption caption) { if (caption == null || TextUtils.isEmpty(caption.getText())) { - binding.caption.setVisibility(View.GONE); - binding.translate.setVisibility(View.GONE); + bottom.caption.setVisibility(View.GONE); + bottom.translate.setVisibility(View.GONE); return; } final String postCaption = caption.getText(); - binding.caption.addOnHashtagListener(autoLinkItem -> { + bottom.caption.addOnHashtagListener(autoLinkItem -> { final NavController navController = NavHostFragment.findNavController(this); final Bundle bundle = new Bundle(); final String originalText = autoLinkItem.getOriginalText().trim(); bundle.putString(ARG_HASHTAG, originalText); navController.navigate(R.id.action_global_hashTagFragment, bundle); }); - binding.caption.addOnMentionClickListener(autoLinkItem -> { + bottom.caption.addOnMentionClickListener(autoLinkItem -> { final String originalText = autoLinkItem.getOriginalText().trim(); navigateToProfile(originalText); }); - binding.caption.addOnEmailClickListener(autoLinkItem -> Utils.openEmailAddress(getContext(), autoLinkItem.getOriginalText().trim())); - binding.caption.addOnURLClickListener(autoLinkItem -> Utils.openURL(getContext(), autoLinkItem.getOriginalText().trim())); - binding.caption.setOnLongClickListener(v -> { + bottom.caption.addOnEmailClickListener(autoLinkItem -> Utils.openEmailAddress(getContext(), autoLinkItem.getOriginalText().trim())); + bottom.caption.addOnURLClickListener(autoLinkItem -> Utils.openURL(getContext(), autoLinkItem.getOriginalText().trim())); + bottom.caption.setOnLongClickListener(v -> { final Context context = getContext(); if (context == null) return false; Utils.copyText(context, postCaption); return true; }); - binding.caption.setText(postCaption); - binding.translate.setOnClickListener(v -> handleTranslateCaptionResource(viewModel.translateCaption())); + bottom.caption.setText(postCaption); + bottom.translate.setOnClickListener(v -> handleTranslateCaptionResource(viewModel.translateCaption())); } private void handleTranslateCaptionResource(@NonNull final LiveData> data) { @@ -631,11 +620,11 @@ public class PostViewV2Fragment extends Fragment implements EditTextDialogFragme if (resource == null) return; switch (resource.status) { case SUCCESS: - binding.translate.setVisibility(View.GONE); - binding.caption.setText(resource.data); + bottom.translate.setVisibility(View.GONE); + bottom.caption.setText(resource.data); break; case ERROR: - binding.translate.setEnabled(true); + bottom.translate.setEnabled(true); String message = resource.message; if (TextUtils.isEmpty(resource.message)) { message = getString(R.string.downloader_unknown_error); @@ -645,7 +634,7 @@ public class PostViewV2Fragment extends Fragment implements EditTextDialogFragme snackbar.show(); break; case LOADING: - binding.translate.setEnabled(false); + bottom.translate.setEnabled(false); break; } }); @@ -671,17 +660,17 @@ public class PostViewV2Fragment extends Fragment implements EditTextDialogFragme private void setupShare() { if (!viewModel.hasPk()) { - binding.share.setVisibility(View.GONE); + bottom.share.setVisibility(View.GONE); return; } - binding.share.setVisibility(View.VISIBLE); - binding.share.setOnLongClickListener(v -> { + bottom.share.setVisibility(View.VISIBLE); + bottom.share.setOnLongClickListener(v -> { final Context context = getContext(); if (context == null) return false; Utils.displayToastAboveView(context, v, getString(R.string.share)); return true; }); - binding.share.setOnClickListener(v -> { + bottom.share.setOnClickListener(v -> { final Media media = viewModel.getMedia(); final User profileModel = media.getUser(); if (profileModel == null) return; @@ -692,7 +681,7 @@ public class PostViewV2Fragment extends Fragment implements EditTextDialogFragme // is this necessary? Toast.makeText(context, R.string.share_private_post, Toast.LENGTH_LONG).show(); } - Intent sharingIntent = new Intent(android.content.Intent.ACTION_SEND); + final Intent sharingIntent = new Intent(android.content.Intent.ACTION_SEND); sharingIntent.setType("text/plain"); sharingIntent.putExtra(android.content.Intent.EXTRA_TEXT, "https://instagram.com/p/" + media.getCode()); startActivity(Intent.createChooser(sharingIntent, @@ -715,82 +704,86 @@ public class PostViewV2Fragment extends Fragment implements EditTextDialogFragme } } - @SuppressLint("ClickableViewAccessibility") private void setupPostImage() { - binding.videoPost.root.setVisibility(View.GONE); - binding.sliderParent.setVisibility(View.GONE); - // binding.playerControlsToggle.setVisibility(View.GONE); - // binding.playerControls.getRoot().setVisibility(View.GONE); binding.mediaCounter.setVisibility(View.GONE); - binding.postImage.setVisibility(View.VISIBLE); + final Context context = getContext(); + if (context == null) return; + final Resources resources = context.getResources(); + if (resources == null) return; final Media media = viewModel.getMedia(); final String imageUrl = ResponseBodyUtils.getImageUrl(media); if (TextUtils.isEmpty(imageUrl)) return; - final ViewGroup.LayoutParams layoutParams = binding.postImage.getLayoutParams(); - final Pair widthHeight = NumberUtils.calculateWidthHeight(media.getOriginalHeight(), - media.getOriginalWidth(), - (int) (Utils.displayMetrics.heightPixels * 0.8), - Utils.displayMetrics.widthPixels); - layoutParams.height = widthHeight.second; - final ImageRequest requestBuilder = ImageRequestBuilder.newBuilderWithSource(Uri.parse(imageUrl)) - .setLocalThumbnailPreviewsEnabled(true) - .build(); - final DraweeController controller = Fresco - .newDraweeControllerBuilder() - .setLowResImageRequest(ImageRequest.fromUri(ResponseBodyUtils.getThumbUrl(media))) - .setImageRequest(requestBuilder) - .build(); - binding.postImage.setController(controller); - // binding.postImage.setOnClickListener(v -> toggleDetails()); - final AnimatedZoomableController zoomableController = (AnimatedZoomableController) binding.postImage.getZoomableController(); + final ZoomableDraweeView postImage = new ZoomableDraweeView(context); + final NullSafePair widthHeight = NumberUtils.calculateWidthHeight(media.getOriginalHeight(), + media.getOriginalWidth(), + (int) (Utils.displayMetrics.heightPixels * 0.8), + Utils.displayMetrics.widthPixels); + final int height = widthHeight.second; + final ConstraintLayout.LayoutParams layoutParams = new ConstraintLayout.LayoutParams(ConstraintLayout.LayoutParams.MATCH_PARENT, height); + layoutParams.topToBottom = binding.topBarrier.getId(); + layoutParams.bottomToTop = bottom.buttonsTopBarrier.getId(); + layoutParams.startToStart = ConstraintLayout.LayoutParams.PARENT_ID; + layoutParams.endToEnd = ConstraintLayout.LayoutParams.PARENT_ID; + postImage.setLayoutParams(layoutParams); + postImage.setHierarchy(new GenericDraweeHierarchyBuilder(resources) + .setActualImageScaleType(ScalingUtils.ScaleType.FIT_CENTER) + .build()); + + postImage.setController(Fresco.newDraweeControllerBuilder() + .setLowResImageRequest(ImageRequest.fromUri(ResponseBodyUtils.getThumbUrl(media))) + .setImageRequest(ImageRequestBuilder.newBuilderWithSource(Uri.parse(imageUrl)) + .setLocalThumbnailPreviewsEnabled(true) + .build()) + .build()); + final AnimatedZoomableController zoomableController = (AnimatedZoomableController) postImage.getZoomableController(); zoomableController.setMaxScaleFactor(3f); zoomableController.setGestureZoomEnabled(true); zoomableController.setEnabled(true); - binding.postImage.setZoomingEnabled(true); - binding.postImage.setTapListener(new GestureDetector.SimpleOnGestureListener() { - @Override - public boolean onSingleTapUp(final MotionEvent e) { - // toggleDetails(); - return true; - } - }); + postImage.setZoomingEnabled(true); + postImage.setTapListener(new DoubleTapGestureListener(postImage)); + binding.contentRoot.addView(postImage, 0); // binding.postImage.setAllowTouchInterceptionWhileZoomed(true); // binding.postImage.setOnVerticalDragListener(onVerticalDragListener); } private void setupSlider() { final Media media = viewModel.getMedia(); - binding.postImage.setVisibility(View.GONE); - binding.videoPost.root.setVisibility(View.GONE); - // binding.playerControlsToggle.setVisibility(View.GONE); - // binding.playerControls.getRoot().setVisibility(View.GONE); - binding.sliderParent.setVisibility(View.VISIBLE); binding.mediaCounter.setVisibility(View.VISIBLE); - final Pair maxHW = media + final Context context = getContext(); + if (context == null) return; + sliderParent = new ViewPager2(context); + final NullSafePair maxHW = media .getCarouselMedia() .stream() - .reduce(new Pair<>(0, 0), + .reduce(new NullSafePair<>(0, 0), (prev, m) -> { final int height = m.getOriginalHeight() > prev.first ? m.getOriginalHeight() : prev.first; final int width = m.getOriginalWidth() > prev.second ? m.getOriginalWidth() : prev.second; - return new Pair<>(height, width); + return new NullSafePair<>(height, width); }, (p1, p2) -> { final int height = p1.first > p2.first ? p1.first : p2.first; final int width = p1.second > p2.second ? p1.second : p2.second; - return new Pair<>(height, width); + return new NullSafePair<>(height, width); }); - final ViewGroup.LayoutParams layoutParams = binding.sliderParent.getLayoutParams(); - final Pair widthHeight = NumberUtils.calculateWidthHeight(maxHW.first, - maxHW.second, - (int) (Utils.displayMetrics.heightPixels * 0.8), - Utils.displayMetrics.widthPixels); - layoutParams.height = widthHeight.second; + final NullSafePair widthHeight = NumberUtils.calculateWidthHeight(maxHW.first, + maxHW.second, + (int) (Utils.displayMetrics.heightPixels * 0.8), + Utils.displayMetrics.widthPixels); + final ConstraintLayout.LayoutParams layoutParams = new ConstraintLayout.LayoutParams(ConstraintLayout.LayoutParams.MATCH_PARENT, + widthHeight.second); + layoutParams.topToBottom = binding.topBarrier.getId(); + layoutParams.bottomToTop = bottom.buttonsTopBarrier.getId(); + layoutParams.startToStart = ConstraintLayout.LayoutParams.PARENT_ID; + layoutParams.endToEnd = ConstraintLayout.LayoutParams.PARENT_ID; + sliderParent.setLayoutParams(layoutParams); + binding.contentRoot.addView(sliderParent, 0); + final boolean hasVideo = media.getCarouselMedia() .stream() .anyMatch(postChild -> postChild.getMediaType() == MediaItemType.MEDIA_TYPE_VIDEO); if (hasVideo) { - final View child = binding.sliderParent.getChildAt(0); + final View child = sliderParent.getChildAt(0); if (child instanceof RecyclerView) { ((RecyclerView) child).setItemViewCacheSize(media.getCarouselMedia().size()); ((RecyclerView) child).addRecyclerListener(holder -> { @@ -837,17 +830,17 @@ public class PostViewV2Fragment extends Fragment implements EditTextDialogFragme Utils.disableKeepScreenOn(activity); } }); - binding.sliderParent.setAdapter(sliderItemsAdapter); + sliderParent.setAdapter(sliderItemsAdapter); if (sliderPosition >= 0 && sliderPosition < media.getCarouselMedia().size()) { - binding.sliderParent.setCurrentItem(sliderPosition); + sliderParent.setCurrentItem(sliderPosition); } - binding.sliderParent.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() { + sliderParent.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() { int prevPosition = -1; @Override public void onPageScrolled(final int position, final float positionOffset, final int positionOffsetPixels) { if (prevPosition != -1) { - final View view = binding.sliderParent.getChildAt(0); + final View view = sliderParent.getChildAt(0); if (view instanceof RecyclerView) { pausePlayerAtPosition(prevPosition, (RecyclerView) view); pausePlayerAtPosition(position, (RecyclerView) view); @@ -905,9 +898,9 @@ public class PostViewV2Fragment extends Fragment implements EditTextDialogFragme } private void pauseSliderPlayer() { - if (binding.sliderParent.getVisibility() != View.VISIBLE) return; - final int currentItem = binding.sliderParent.getCurrentItem(); - final View view = binding.sliderParent.getChildAt(0); + if (sliderParent == null) return; + final int currentItem = sliderParent.getCurrentItem(); + final View view = sliderParent.getChildAt(0); if (!(view instanceof RecyclerView)) return; final RecyclerView.ViewHolder viewHolder = ((RecyclerView) view).findViewHolderForAdapterPosition(currentItem); if (!(viewHolder instanceof SliderVideoViewHolder)) return; @@ -915,8 +908,8 @@ public class PostViewV2Fragment extends Fragment implements EditTextDialogFragme } private void releaseAllSliderPlayers() { - if (binding.sliderParent.getVisibility() != View.VISIBLE) return; - final View view = binding.sliderParent.getChildAt(0); + if (sliderParent == null) return; + final View view = sliderParent.getChildAt(0); if (!(view instanceof RecyclerView)) return; final int itemCount = sliderItemsAdapter.getItemCount(); for (int position = itemCount - 1; position >= 0; position--) { @@ -926,30 +919,38 @@ public class PostViewV2Fragment extends Fragment implements EditTextDialogFragme } } - @SuppressLint("ClickableViewAccessibility") private void setupVideo() { video = true; final Media media = viewModel.getMedia(); - binding.postImage.setVisibility(View.GONE); - binding.sliderParent.setVisibility(View.GONE); binding.mediaCounter.setVisibility(View.GONE); - // binding.playerControls.getRoot().setVisibility(View.VISIBLE); - final ViewGroup.LayoutParams layoutParams = binding.videoPost.root.getLayoutParams(); - final Pair widthHeight = NumberUtils.calculateWidthHeight(media.getOriginalHeight(), - media.getOriginalWidth(), - (int) (Utils.displayMetrics.heightPixels * 0.8), - Utils.displayMetrics.widthPixels); - layoutParams.height = widthHeight.second; - binding.videoPost.root.setVisibility(View.VISIBLE); - // enablePlayerControls(true); - // binding.videoPost.playerView.setOnClickListener(v -> toggleDetails()); final Context context = getContext(); if (context == null) return; - final GestureDetector gestureDetector = new GestureDetector(context, videoPlayerViewGestureListener); - binding.videoPost.playerView.setOnTouchListener((v, event) -> { - gestureDetector.onTouchEvent(event); - return true; - }); + final LayoutVideoPlayerWithThumbnailBinding videoPost = LayoutVideoPlayerWithThumbnailBinding + .inflate(LayoutInflater.from(context), binding.contentRoot, false); + final ConstraintLayout.LayoutParams layoutParams = (ConstraintLayout.LayoutParams) videoPost.getRoot().getLayoutParams(); + final NullSafePair widthHeight = NumberUtils.calculateWidthHeight(media.getOriginalHeight(), + media.getOriginalWidth(), + (int) (Utils.displayMetrics.heightPixels * 0.8), + Utils.displayMetrics.widthPixels); + layoutParams.width = ConstraintLayout.LayoutParams.MATCH_PARENT; + layoutParams.height = widthHeight.second; + layoutParams.topToBottom = binding.topBarrier.getId(); + layoutParams.bottomToTop = bottom.buttonsTopBarrier.getId(); + layoutParams.startToStart = ConstraintLayout.LayoutParams.PARENT_ID; + layoutParams.endToEnd = ConstraintLayout.LayoutParams.PARENT_ID; + binding.contentRoot.addView(videoPost.getRoot(), 0); + + // final GestureDetector gestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() { + // @Override + // public boolean onSingleTapConfirmed(final MotionEvent e) { + // videoPost.playerView.performClick(); + // return true; + // } + // }); + // videoPost.playerView.setOnTouchListener((v, event) -> { + // gestureDetector.onTouchEvent(event); + // return true; + // }); final float vol = settingsHelper.getBoolean(Constants.MUTED_VIDEOS) ? 0f : 1f; final VideoPlayerViewHelper.VideoPlayerCallback videoPlayerCallback = new VideoPlayerCallbackAdapter() { @Override @@ -960,13 +961,13 @@ public class PostViewV2Fragment extends Fragment implements EditTextDialogFragme @Override public void onPlayerViewLoaded() { // binding.playerControls.getRoot().setVisibility(View.VISIBLE); - final ViewGroup.LayoutParams layoutParams = binding.videoPost.playerView.getLayoutParams(); + final ViewGroup.LayoutParams layoutParams = videoPost.playerView.getLayoutParams(); final int requiredWidth = Utils.displayMetrics.widthPixels; final int resultingHeight = NumberUtils .getResultingHeight(requiredWidth, media.getOriginalHeight(), media.getOriginalWidth()); layoutParams.width = requiredWidth; layoutParams.height = resultingHeight; - binding.videoPost.playerView.requestLayout(); + videoPost.playerView.requestLayout(); } @Override @@ -1005,13 +1006,12 @@ public class PostViewV2Fragment extends Fragment implements EditTextDialogFragme if (videoUrl != null) { videoPlayerViewHelper = new VideoPlayerViewHelper( binding.getRoot().getContext(), - binding.videoPost, + videoPost, videoUrl, vol, aspectRatio, ResponseBodyUtils.getThumbUrl(media), true, - // /*binding.playerControls*/null, videoPlayerCallback); } } @@ -1234,7 +1234,7 @@ public class PostViewV2Fragment extends Fragment implements EditTextDialogFragme } private void toggleDetails() { - hasBeenToggled = true; + // hasBeenToggled = true; final Media media = viewModel.getMedia(); binding.getRoot().post(() -> { TransitionManager.beginDelayedTransition(binding.getRoot()); @@ -1249,20 +1249,17 @@ public class PostViewV2Fragment extends Fragment implements EditTextDialogFragme if (media.getLocation() != null) { binding.location.setVisibility(View.GONE); } - // binding.captionParent.setVisibility(View.GONE); - binding.bottomBg.setVisibility(View.GONE); - binding.likesCount.setVisibility(View.GONE); - binding.commentsCount.setVisibility(View.GONE); - binding.date.setVisibility(View.GONE); - binding.comment.setVisibility(View.GONE); - // binding.captionToggle.setVisibility(View.GONE); - // binding.playerControlsToggle.setVisibility(View.GONE); - binding.like.setVisibility(View.GONE); - binding.save.setVisibility(View.GONE); - binding.share.setVisibility(View.GONE); - binding.download.setVisibility(View.GONE); + // bottom.bottomBg.setVisibility(View.GONE); + // bottom.likesCount.setVisibility(View.GONE); + // bottom.commentsCount.setVisibility(View.GONE); + bottom.date.setVisibility(View.GONE); + bottom.comment.setVisibility(View.GONE); + bottom.like.setVisibility(View.GONE); + bottom.save.setVisibility(View.GONE); + // bottom.share.setVisibility(View.GONE); + bottom.download.setVisibility(View.GONE); binding.mediaCounter.setVisibility(View.GONE); - binding.viewsCount.setVisibility(View.GONE); + bottom.viewsCount.setVisibility(View.GONE); final List options = viewModel.getOptions().getValue(); if (options != null && !options.isEmpty()) { binding.options.setVisibility(View.GONE); @@ -1282,30 +1279,30 @@ public class PostViewV2Fragment extends Fragment implements EditTextDialogFragme if (media.getLocation() != null) { binding.location.setVisibility(View.VISIBLE); } - binding.bottomBg.setVisibility(View.VISIBLE); + // bottom.bottomBg.setVisibility(View.VISIBLE); if (viewModel.hasPk()) { - binding.likesCount.setVisibility(View.VISIBLE); - binding.date.setVisibility(View.VISIBLE); + // bottom.likesCount.setVisibility(View.VISIBLE); + bottom.date.setVisibility(View.VISIBLE); // binding.captionParent.setVisibility(View.VISIBLE); // binding.captionToggle.setVisibility(View.VISIBLE); - binding.share.setVisibility(View.VISIBLE); + // bottom.share.setVisibility(View.VISIBLE); } if (viewModel.hasPk() && !viewModel.getMedia().isCommentsDisabled()) { - binding.comment.setVisibility(View.VISIBLE); - binding.commentsCount.setVisibility(View.VISIBLE); + bottom.comment.setVisibility(View.VISIBLE); + // bottom.commentsCount.setVisibility(View.VISIBLE); } - binding.download.setVisibility(View.VISIBLE); + bottom.download.setVisibility(View.VISIBLE); final List options = viewModel.getOptions().getValue(); if (options != null && !options.isEmpty()) { binding.options.setVisibility(View.VISIBLE); } if (viewModel.isLoggedIn() && viewModel.hasPk()) { - binding.like.setVisibility(View.VISIBLE); - binding.save.setVisibility(View.VISIBLE); + bottom.like.setVisibility(View.VISIBLE); + bottom.save.setVisibility(View.VISIBLE); } if (video) { // binding.playerControlsToggle.setVisibility(View.VISIBLE); - binding.viewsCount.setVisibility(View.VISIBLE); + bottom.viewsCount.setVisibility(View.VISIBLE); } // if (wasControlsVisible) { // showPlayerControls(); diff --git a/app/src/main/java/awais/instagrabber/utils/NullSafePair.java b/app/src/main/java/awais/instagrabber/utils/NullSafePair.java new file mode 100644 index 00000000..43c5a7d4 --- /dev/null +++ b/app/src/main/java/awais/instagrabber/utils/NullSafePair.java @@ -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 { + 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 androidx.core.util.Pair create(@Nullable A a, @Nullable B b) { + return new androidx.core.util.Pair(a, b); + } +} + diff --git a/app/src/main/java/awais/instagrabber/utils/NumberUtils.java b/app/src/main/java/awais/instagrabber/utils/NumberUtils.java index 947165e9..5ad9ceea 100644 --- a/app/src/main/java/awais/instagrabber/utils/NumberUtils.java +++ b/app/src/main/java/awais/instagrabber/utils/NumberUtils.java @@ -1,8 +1,9 @@ package awais.instagrabber.utils; import androidx.annotation.NonNull; -import androidx.core.util.Pair; +import androidx.annotation.Nullable; +import java.util.Locale; import java.util.Random; public final class NumberUtils { @@ -56,7 +57,7 @@ public final class NumberUtils { } @NonNull - public static Pair calculateWidthHeight(final int height, final int width, final int maxHeight, final int maxWidth) { + public static NullSafePair calculateWidthHeight(final int height, final int width, final int maxHeight, final int maxWidth) { if (width > maxWidth) { int tempHeight = getResultingHeight(maxWidth, height, width); int tempWidth = maxWidth; @@ -64,7 +65,7 @@ public final class NumberUtils { tempWidth = getResultingWidth(maxHeight, tempHeight, tempWidth); tempHeight = maxHeight; } - return new Pair<>(tempWidth, tempHeight); + return new NullSafePair<>(tempWidth, tempHeight); } if ((height < maxHeight && width < maxWidth) || (height > maxHeight)) { int tempWidth = getResultingWidth(maxHeight, height, width); @@ -73,12 +74,76 @@ public final class NumberUtils { tempHeight = getResultingHeight(maxWidth, tempHeight, tempWidth); tempWidth = maxWidth; } - return new Pair<>(tempWidth, tempHeight); + return new NullSafePair<>(tempWidth, tempHeight); } - return new Pair<>(width, height); + return new NullSafePair<>(width, height); } public static float roundFloat2Decimals(final float value) { return ((int) ((value + (value >= 0 ? 1 : -1) * 0.005f) * 100)) / 100f; } + + @NonNull + public static String abbreviate(final long number) { + return abbreviate(number, null); + } + + + @NonNull + public static String abbreviate(final long number, @Nullable final AbbreviateOptions options) { + // adapted from https://stackoverflow.com/a/9769590/1436766 + int threshold = 1000; + boolean addSpace = false; + if (options != null) { + threshold = options.getThreshold(); + addSpace = options.addSpaceBeforePrefix(); + } + if (number < threshold) return "" + number; + int exp = (int) (Math.log(number) / Math.log(threshold)); + return String.format(Locale.US, + "%.1f%s%c", + number / Math.pow(threshold, exp), + addSpace ? " " : "", + "kMGTPE".charAt(exp - 1)); + } + + public static final class AbbreviateOptions { + private final int threshold; + private final boolean addSpaceBeforePrefix; + + public static final class Builder { + + private int threshold = 1000; + private boolean addSpaceBeforePrefix = false; + + public Builder setThreshold(final int threshold) { + this.threshold = threshold; + return this; + } + + public Builder setAddSpaceBeforePrefix(final boolean addSpaceBeforePrefix) { + this.addSpaceBeforePrefix = addSpaceBeforePrefix; + return this; + } + + @NonNull + public AbbreviateOptions build() { + return new AbbreviateOptions(threshold, addSpaceBeforePrefix); + } + + } + + private AbbreviateOptions(final int threshold, final boolean addSpaceBeforePrefix) { + this.threshold = threshold; + this.addSpaceBeforePrefix = addSpaceBeforePrefix; + } + + public int getThreshold() { + return threshold; + } + + public boolean addSpaceBeforePrefix() { + return addSpaceBeforePrefix; + } + } } diff --git a/app/src/main/res/drawable/ic_bookmark.xml b/app/src/main/res/drawable/ic_bookmark.xml index 69bc5089..35986396 100644 --- a/app/src/main/res/drawable/ic_bookmark.xml +++ b/app/src/main/res/drawable/ic_bookmark.xml @@ -1,10 +1,10 @@ - + android:viewportHeight="24"> + diff --git a/app/src/main/res/drawable/ic_round_bookmark_border_24.xml b/app/src/main/res/drawable/ic_round_bookmark_border_24.xml new file mode 100644 index 00000000..e0c532fe --- /dev/null +++ b/app/src/main/res/drawable/ic_round_bookmark_border_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/layout/dialog_post_view.xml b/app/src/main/res/layout/dialog_post_view.xml index 7778d7e5..76fbbbd2 100644 --- a/app/src/main/res/layout/dialog_post_view.xml +++ b/app/src/main/res/layout/dialog_post_view.xml @@ -3,59 +3,14 @@ xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" - android:layout_height="match_parent"> + android:layout_height="match_parent" + tools:context=".fragments.PostViewV2Fragment"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_exo_custom_controls.xml b/app/src/main/res/layout/layout_exo_custom_controls.xml index 9ad77784..8b9c7d40 100644 --- a/app/src/main/res/layout/layout_exo_custom_controls.xml +++ b/app/src/main/res/layout/layout_exo_custom_controls.xml @@ -12,47 +12,13 @@ app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toTopOf="@id/exo_position" /> - - - - - - + app:layout_constraintTop_toTopOf="@id/top_barrier" /> + app:barrierDirection="top" /> + tools:visibility="gone" /> @@ -94,12 +60,12 @@ app:icon="@drawable/ic_forward_5_24_states" app:iconSize="24dp" app:iconTint="@color/white" - app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintBottom_toTopOf="@id/progress_barrier" app:layout_constraintEnd_toStartOf="@id/mute" app:layout_constraintStart_toEndOf="@id/exo_play_pause" - app:layout_constraintTop_toBottomOf="@id/progress_barrier" + app:layout_constraintTop_toBottomOf="@id/top_barrier" tools:enabled="false" - tools:visibility="visible" /> + tools:visibility="gone" /> @@ -124,10 +90,53 @@ app:icon="@drawable/exo_ic_settings" app:iconSize="24dp" app:iconTint="@color/white" - app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintBottom_toTopOf="@id/progress_barrier" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toEndOf="@id/mute" - app:layout_constraintTop_toBottomOf="@id/progress_barrier" + app:layout_constraintTop_toBottomOf="@id/top_barrier" tools:enabled="false" tools:visibility="visible" /> + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_post_view_bottom.xml b/app/src/main/res/layout/layout_post_view_bottom.xml new file mode 100644 index 00000000..e800101a --- /dev/null +++ b/app/src/main/res/layout/layout_post_view_bottom.xml @@ -0,0 +1,203 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_video_player_with_thumbnail.xml b/app/src/main/res/layout/layout_video_player_with_thumbnail.xml index 2c2bed4f..a467fff2 100644 --- a/app/src/main/res/layout/layout_video_player_with_thumbnail.xml +++ b/app/src/main/res/layout/layout_video_player_with_thumbnail.xml @@ -1,7 +1,6 @@ @@ -15,7 +14,7 @@ android:id="@+id/thumbnail" android:layout_width="match_parent" android:layout_height="wrap_content" - app:actualImageScaleType="centerCrop" + app:actualImageScaleType="fitCenter" app:viewAspectRatio="1" /> + + android:id="@+id/playerView" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_gravity="center" + app:resize_mode="fixed_width" + app:show_timeout="2000" + app:use_controller="true" /> \ No newline at end of file diff --git a/app/src/main/res/values/drawables.xml b/app/src/main/res/values/drawables.xml index 5df15b97..0c0d1933 100644 --- a/app/src/main/res/values/drawables.xml +++ b/app/src/main/res/values/drawables.xml @@ -1,5 +1,5 @@ - @drawable/ic_play_arrow_24 - @drawable/ic_pause_24 + + \ No newline at end of file