Ammar Githam
4 years ago
33 changed files with 1405 additions and 148 deletions
-
44app/src/main/java/awais/instagrabber/adapters/DirectItemsAdapter.java
-
18app/src/main/java/awais/instagrabber/adapters/viewholder/DirectInboxItemViewHolder.java
-
1app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemLinkViewHolder.java
-
31app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemMediaShareViewHolder.java
-
184app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemViewHolder.java
-
1app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemVoiceMediaViewHolder.java
-
84app/src/main/java/awais/instagrabber/animations/RevealOutlineAnimation.java
-
56app/src/main/java/awais/instagrabber/animations/RoundedRectRevealOutlineProvider.java
-
3app/src/main/java/awais/instagrabber/customviews/ChatMessageLayout.java
-
457app/src/main/java/awais/instagrabber/customviews/DirectItemContextMenu.java
-
110app/src/main/java/awais/instagrabber/customviews/DirectItemFrameLayout.java
-
55app/src/main/java/awais/instagrabber/customviews/PopupDialog.java
-
76app/src/main/java/awais/instagrabber/customviews/emoji/ReactionsManager.java
-
36app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageThreadFragment.java
-
7app/src/main/java/awais/instagrabber/repositories/requests/directmessages/ReactionBroadcastOptions.java
-
6app/src/main/java/awais/instagrabber/repositories/responses/directmessages/DirectItem.java
-
18app/src/main/java/awais/instagrabber/repositories/responses/directmessages/DirectItemEmojiReaction.java
-
37app/src/main/java/awais/instagrabber/repositories/responses/directmessages/DirectItemReactions.java
-
1app/src/main/java/awais/instagrabber/utils/Constants.java
-
3app/src/main/java/awais/instagrabber/utils/SettingsHelper.java
-
7app/src/main/java/awais/instagrabber/utils/emoji/EmojiParser.java
-
114app/src/main/java/awais/instagrabber/viewmodels/DirectThreadViewModel.java
-
10app/src/main/java/awais/instagrabber/webservices/DirectMessagesService.java
-
12app/src/main/res/drawable/bg_rounded_corner.xml
-
10app/src/main/res/drawable/ic_add.xml
-
10app/src/main/res/layout/fragment_direct_messages_thread.xml
-
2app/src/main/res/layout/item_pref_divider.xml
-
69app/src/main/res/layout/layout_direct_item_options.xml
-
70app/src/main/res/layout/layout_dm_base.xml
-
5app/src/main/res/layout/layout_dm_media_share.xml
-
10app/src/main/res/values/dimens.xml
-
5app/src/main/res/values/ids.xml
-
1app/src/main/res/values/strings.xml
@ -0,0 +1,84 @@ |
|||||
|
package awais.instagrabber.animations; |
||||
|
|
||||
|
import android.animation.Animator; |
||||
|
import android.animation.AnimatorListenerAdapter; |
||||
|
import android.animation.ValueAnimator; |
||||
|
import android.graphics.Outline; |
||||
|
import android.graphics.Rect; |
||||
|
import android.view.View; |
||||
|
import android.view.ViewOutlineProvider; |
||||
|
|
||||
|
/** |
||||
|
* A {@link ViewOutlineProvider} that has helper functions to create reveal animations. |
||||
|
* This class should be extended so that subclasses can define the reveal shape as the |
||||
|
* animation progresses from 0 to 1. |
||||
|
*/ |
||||
|
public abstract class RevealOutlineAnimation extends ViewOutlineProvider { |
||||
|
protected Rect mOutline; |
||||
|
protected float mOutlineRadius; |
||||
|
|
||||
|
public RevealOutlineAnimation() { |
||||
|
mOutline = new Rect(); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Returns whether elevation should be removed for the duration of the reveal animation. |
||||
|
*/ |
||||
|
abstract boolean shouldRemoveElevationDuringAnimation(); |
||||
|
|
||||
|
/** |
||||
|
* Sets the progress, from 0 to 1, of the reveal animation. |
||||
|
*/ |
||||
|
abstract void setProgress(float progress); |
||||
|
|
||||
|
public ValueAnimator createRevealAnimator(final View revealView, boolean isReversed) { |
||||
|
ValueAnimator va = |
||||
|
isReversed ? ValueAnimator.ofFloat(1f, 0f) : ValueAnimator.ofFloat(0f, 1f); |
||||
|
final float elevation = revealView.getElevation(); |
||||
|
|
||||
|
va.addListener(new AnimatorListenerAdapter() { |
||||
|
private boolean mIsClippedToOutline; |
||||
|
private ViewOutlineProvider mOldOutlineProvider; |
||||
|
|
||||
|
public void onAnimationStart(Animator animation) { |
||||
|
mIsClippedToOutline = revealView.getClipToOutline(); |
||||
|
mOldOutlineProvider = revealView.getOutlineProvider(); |
||||
|
|
||||
|
revealView.setOutlineProvider(RevealOutlineAnimation.this); |
||||
|
revealView.setClipToOutline(true); |
||||
|
if (shouldRemoveElevationDuringAnimation()) { |
||||
|
revealView.setTranslationZ(-elevation); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public void onAnimationEnd(Animator animation) { |
||||
|
revealView.setOutlineProvider(mOldOutlineProvider); |
||||
|
revealView.setClipToOutline(mIsClippedToOutline); |
||||
|
if (shouldRemoveElevationDuringAnimation()) { |
||||
|
revealView.setTranslationZ(0); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
}); |
||||
|
|
||||
|
va.addUpdateListener(v -> { |
||||
|
float progress = (Float) v.getAnimatedValue(); |
||||
|
setProgress(progress); |
||||
|
revealView.invalidateOutline(); |
||||
|
}); |
||||
|
return va; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void getOutline(View v, Outline outline) { |
||||
|
outline.setRoundRect(mOutline, mOutlineRadius); |
||||
|
} |
||||
|
|
||||
|
public float getRadius() { |
||||
|
return mOutlineRadius; |
||||
|
} |
||||
|
|
||||
|
public void getOutline(Rect out) { |
||||
|
out.set(mOutline); |
||||
|
} |
||||
|
} |
@ -0,0 +1,56 @@ |
|||||
|
/* |
||||
|
* Copyright (C) 2017 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. |
||||
|
*/ |
||||
|
|
||||
|
package awais.instagrabber.animations; |
||||
|
|
||||
|
import android.graphics.Rect; |
||||
|
|
||||
|
/** |
||||
|
* A {@link RevealOutlineAnimation} that provides an outline that interpolates between two radii |
||||
|
* and two {@link Rect}s. |
||||
|
* <p> |
||||
|
* An example usage of this provider is an outline that starts out as a circle and ends |
||||
|
* as a rounded rectangle. |
||||
|
*/ |
||||
|
public class RoundedRectRevealOutlineProvider extends RevealOutlineAnimation { |
||||
|
private final float mStartRadius; |
||||
|
private final float mEndRadius; |
||||
|
|
||||
|
private final Rect mStartRect; |
||||
|
private final Rect mEndRect; |
||||
|
|
||||
|
public RoundedRectRevealOutlineProvider(float startRadius, float endRadius, Rect startRect, Rect endRect) { |
||||
|
mStartRadius = startRadius; |
||||
|
mEndRadius = endRadius; |
||||
|
mStartRect = startRect; |
||||
|
mEndRect = endRect; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public boolean shouldRemoveElevationDuringAnimation() { |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void setProgress(float progress) { |
||||
|
mOutlineRadius = (1 - progress) * mStartRadius + progress * mEndRadius; |
||||
|
|
||||
|
mOutline.left = (int) ((1 - progress) * mStartRect.left + progress * mEndRect.left); |
||||
|
mOutline.top = (int) ((1 - progress) * mStartRect.top + progress * mEndRect.top); |
||||
|
mOutline.right = (int) ((1 - progress) * mStartRect.right + progress * mEndRect.right); |
||||
|
mOutline.bottom = (int) ((1 - progress) * mStartRect.bottom + progress * mEndRect.bottom); |
||||
|
} |
||||
|
} |
@ -0,0 +1,457 @@ |
|||||
|
package awais.instagrabber.customviews; |
||||
|
|
||||
|
import android.animation.Animator; |
||||
|
import android.animation.AnimatorListenerAdapter; |
||||
|
import android.animation.AnimatorSet; |
||||
|
import android.animation.TimeInterpolator; |
||||
|
import android.animation.ValueAnimator; |
||||
|
import android.content.Context; |
||||
|
import android.graphics.Point; |
||||
|
import android.graphics.Rect; |
||||
|
import android.util.TypedValue; |
||||
|
import android.view.Gravity; |
||||
|
import android.view.LayoutInflater; |
||||
|
import android.view.View; |
||||
|
import android.view.ViewGroup; |
||||
|
import android.view.animation.AccelerateDecelerateInterpolator; |
||||
|
import android.widget.ImageView; |
||||
|
import android.widget.PopupWindow; |
||||
|
|
||||
|
import androidx.annotation.IdRes; |
||||
|
import androidx.annotation.NonNull; |
||||
|
import androidx.annotation.Nullable; |
||||
|
import androidx.annotation.StringRes; |
||||
|
import androidx.appcompat.widget.AppCompatEditText; |
||||
|
import androidx.appcompat.widget.AppCompatImageView; |
||||
|
import androidx.appcompat.widget.AppCompatTextView; |
||||
|
import androidx.constraintlayout.widget.ConstraintLayout; |
||||
|
import androidx.core.util.Pair; |
||||
|
|
||||
|
import java.util.List; |
||||
|
|
||||
|
import awais.instagrabber.R; |
||||
|
import awais.instagrabber.animations.RoundedRectRevealOutlineProvider; |
||||
|
import awais.instagrabber.customviews.emoji.Emoji; |
||||
|
import awais.instagrabber.customviews.emoji.ReactionsManager; |
||||
|
import awais.instagrabber.databinding.LayoutDirectItemOptionsBinding; |
||||
|
|
||||
|
import static android.view.View.MeasureSpec.makeMeasureSpec; |
||||
|
|
||||
|
public class DirectItemContextMenu extends PopupWindow { |
||||
|
private static final String TAG = DirectItemContextMenu.class.getSimpleName(); |
||||
|
private static final int DO_NOT_UPDATE_FLAG = -1; |
||||
|
private static final int DURATION = 300; |
||||
|
|
||||
|
private final Context context; |
||||
|
private final boolean showReactions; |
||||
|
private final ReactionsManager reactionsManager; |
||||
|
private final int emojiSize; |
||||
|
private final int emojiMargin; |
||||
|
private final int emojiMarginHalf; |
||||
|
private final Rect startRect = new Rect(); |
||||
|
private final Rect endRect = new Rect(); |
||||
|
private final TimeInterpolator revealInterpolator = new AccelerateDecelerateInterpolator(); |
||||
|
private final AnimatorListenerAdapter exitAnimationListener; |
||||
|
private final TypedValue selectableItemBackgroundBorderless; |
||||
|
private final TypedValue selectableItemBackground; |
||||
|
private final int dividerHeight; |
||||
|
private final int optionHeight; |
||||
|
private final int optionPadding; |
||||
|
private final int addAdjust; |
||||
|
private final boolean hasOptions; |
||||
|
private final List<MenuItem> options; |
||||
|
|
||||
|
/* = ImmutableList.of( |
||||
|
new MenuItem(R.id.reply, R.string.reply), |
||||
|
new MenuItem(R.id.unsend, R.string.dms_inbox_unsend) |
||||
|
);*/ |
||||
|
private AnimatorSet openCloseAnimator; |
||||
|
private Point location; |
||||
|
private Point point; |
||||
|
private OnReactionClickListener onReactionClickListener; |
||||
|
private OnOptionSelectListener onOptionSelectListener; |
||||
|
private OnAddReactionClickListener onAddReactionListener; |
||||
|
|
||||
|
public DirectItemContextMenu(@NonNull final Context context, final boolean showReactions, final List<MenuItem> options) { |
||||
|
super(context); |
||||
|
this.context = context; |
||||
|
this.showReactions = showReactions; |
||||
|
this.options = options; |
||||
|
if (!showReactions && (options == null || options.isEmpty())) { |
||||
|
throw new IllegalArgumentException("showReactions is set false and options are empty"); |
||||
|
} |
||||
|
reactionsManager = ReactionsManager.getInstance(); |
||||
|
emojiSize = context.getResources().getDimensionPixelSize(R.dimen.reaction_picker_emoji_size); |
||||
|
emojiMargin = context.getResources().getDimensionPixelSize(R.dimen.reaction_picker_emoji_margin); |
||||
|
emojiMarginHalf = emojiMargin / 2; |
||||
|
addAdjust = context.getResources().getDimensionPixelSize(R.dimen.reaction_picker_add_padding_adjustment); |
||||
|
dividerHeight = context.getResources().getDimensionPixelSize(R.dimen.horizontal_divider_height); |
||||
|
optionHeight = context.getResources().getDimensionPixelSize(R.dimen.reaction_picker_option_height); |
||||
|
optionPadding = context.getResources().getDimensionPixelSize(R.dimen.dm_message_card_radius); |
||||
|
exitAnimationListener = new AnimatorListenerAdapter() { |
||||
|
@Override |
||||
|
public void onAnimationEnd(final Animator animation) { |
||||
|
openCloseAnimator = null; |
||||
|
point = null; |
||||
|
getContentView().post(DirectItemContextMenu.super::dismiss); |
||||
|
} |
||||
|
}; |
||||
|
selectableItemBackgroundBorderless = new TypedValue(); |
||||
|
context.getTheme().resolveAttribute(android.R.attr.selectableItemBackgroundBorderless, selectableItemBackgroundBorderless, true); |
||||
|
selectableItemBackground = new TypedValue(); |
||||
|
context.getTheme().resolveAttribute(android.R.attr.selectableItemBackground, selectableItemBackground, true); |
||||
|
hasOptions = options != null && !options.isEmpty(); |
||||
|
} |
||||
|
|
||||
|
public void show(@NonNull View rootView, @NonNull final Point location) { |
||||
|
final View content = createContentView(); |
||||
|
content.measure(makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)); |
||||
|
setup(content); |
||||
|
// rootView.getParent().requestDisallowInterceptTouchEvent(true); |
||||
|
// final Point correctedLocation = new Point(location.x, location.y - emojiSize * 2); |
||||
|
this.location = location; |
||||
|
showAtLocation(rootView, Gravity.TOP | Gravity.START, location.x, location.y); |
||||
|
// fixPopupLocation(popupWindow, correctedLocation); |
||||
|
animateOpen(); |
||||
|
} |
||||
|
|
||||
|
private void setup(final View content) { |
||||
|
setContentView(content); |
||||
|
setWindowLayoutMode(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); |
||||
|
setFocusable(true); |
||||
|
setOutsideTouchable(true); |
||||
|
setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED); |
||||
|
setBackgroundDrawable(null); |
||||
|
} |
||||
|
|
||||
|
public void setOnOptionSelectListener(final OnOptionSelectListener onOptionSelectListener) { |
||||
|
this.onOptionSelectListener = onOptionSelectListener; |
||||
|
} |
||||
|
|
||||
|
public void setOnReactionClickListener(final OnReactionClickListener onReactionClickListener) { |
||||
|
this.onReactionClickListener = onReactionClickListener; |
||||
|
} |
||||
|
|
||||
|
public void setOnAddReactionListener(final OnAddReactionClickListener onAddReactionListener) { |
||||
|
this.onAddReactionListener = onAddReactionListener; |
||||
|
} |
||||
|
|
||||
|
private void animateOpen() { |
||||
|
final View contentView = getContentView(); |
||||
|
contentView.setVisibility(View.INVISIBLE); |
||||
|
contentView.post(() -> { |
||||
|
final AnimatorSet openAnim = new AnimatorSet(); |
||||
|
// Rectangular reveal. |
||||
|
final ValueAnimator revealAnim = createOpenCloseOutlineProvider().createRevealAnimator(contentView, false); |
||||
|
revealAnim.setDuration(DURATION); |
||||
|
revealAnim.setInterpolator(revealInterpolator); |
||||
|
|
||||
|
ValueAnimator fadeIn = ValueAnimator.ofFloat(0, 1); |
||||
|
fadeIn.setDuration(DURATION); |
||||
|
fadeIn.setInterpolator(revealInterpolator); |
||||
|
fadeIn.addUpdateListener(anim -> { |
||||
|
float alpha = (float) anim.getAnimatedValue(); |
||||
|
contentView.setAlpha(revealAnim.isStarted() ? alpha : 0); |
||||
|
}); |
||||
|
openAnim.play(fadeIn); |
||||
|
openAnim.addListener(new AnimatorListenerAdapter() { |
||||
|
@Override |
||||
|
public void onAnimationEnd(Animator animation) { |
||||
|
contentView.setAlpha(1f); |
||||
|
openCloseAnimator = null; |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
openCloseAnimator = openAnim; |
||||
|
openAnim.playSequentially(revealAnim); |
||||
|
contentView.setVisibility(View.VISIBLE); |
||||
|
openAnim.start(); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
protected void animateClose() { |
||||
|
endRect.setEmpty(); |
||||
|
if (openCloseAnimator != null) { |
||||
|
openCloseAnimator.cancel(); |
||||
|
} |
||||
|
final View contentView = getContentView(); |
||||
|
final AnimatorSet closeAnim = new AnimatorSet(); |
||||
|
// Rectangular reveal (reversed). |
||||
|
final ValueAnimator revealAnim = createOpenCloseOutlineProvider().createRevealAnimator(contentView, true); |
||||
|
revealAnim.setDuration(DURATION); |
||||
|
revealAnim.setInterpolator(revealInterpolator); |
||||
|
closeAnim.play(revealAnim); |
||||
|
|
||||
|
ValueAnimator fadeOut = ValueAnimator.ofFloat(contentView.getAlpha(), 0); |
||||
|
fadeOut.setDuration(DURATION); |
||||
|
fadeOut.setInterpolator(revealInterpolator); |
||||
|
fadeOut.addUpdateListener(anim -> { |
||||
|
float alpha = (float) anim.getAnimatedValue(); |
||||
|
contentView.setAlpha(revealAnim.isStarted() ? alpha : contentView.getAlpha()); |
||||
|
}); |
||||
|
closeAnim.playTogether(fadeOut); |
||||
|
closeAnim.addListener(exitAnimationListener); |
||||
|
openCloseAnimator = closeAnim; |
||||
|
closeAnim.start(); |
||||
|
} |
||||
|
|
||||
|
private RoundedRectRevealOutlineProvider createOpenCloseOutlineProvider() { |
||||
|
final View contentView = getContentView(); |
||||
|
final int radius = context.getResources().getDimensionPixelSize(R.dimen.dm_message_card_radius_small); |
||||
|
// Log.d(TAG, "createOpenCloseOutlineProvider: " + locationOnScreen(contentView) + " " + contentView.getMeasuredWidth() + " " + contentView |
||||
|
// .getMeasuredHeight()); |
||||
|
if (point == null) { |
||||
|
point = locationOnScreen(contentView); |
||||
|
} |
||||
|
final int left = location.x - point.x; |
||||
|
final int top = location.y - point.y; |
||||
|
startRect.set(left, top, left, top); |
||||
|
endRect.set(0, 0, contentView.getMeasuredWidth(), contentView.getMeasuredHeight()); |
||||
|
return new RoundedRectRevealOutlineProvider(radius, radius, startRect, endRect); |
||||
|
} |
||||
|
|
||||
|
public void dismiss() { |
||||
|
animateClose(); |
||||
|
} |
||||
|
|
||||
|
private View createContentView() { |
||||
|
final LayoutInflater layoutInflater = LayoutInflater.from(context); |
||||
|
final LayoutDirectItemOptionsBinding binding = LayoutDirectItemOptionsBinding.inflate(layoutInflater, null, false); |
||||
|
Pair<View, View> firstLastEmojiView = null; |
||||
|
if (showReactions) { |
||||
|
firstLastEmojiView = addReactions(layoutInflater, binding.container); |
||||
|
} |
||||
|
if (hasOptions) { |
||||
|
View divider = null; |
||||
|
if (showReactions) { |
||||
|
if (firstLastEmojiView == null) { |
||||
|
throw new IllegalStateException("firstLastEmojiView is null even though reactions were added"); |
||||
|
} |
||||
|
// add divider if reactions were added |
||||
|
divider = addDivider(binding.container, |
||||
|
firstLastEmojiView.first.getId(), |
||||
|
firstLastEmojiView.first.getId(), |
||||
|
firstLastEmojiView.second.getId()); |
||||
|
((ConstraintLayout.LayoutParams) firstLastEmojiView.first.getLayoutParams()).bottomToTop = divider.getId(); |
||||
|
} |
||||
|
addOptions(layoutInflater, binding.container, divider); |
||||
|
} |
||||
|
return binding.getRoot(); |
||||
|
} |
||||
|
|
||||
|
private Pair<View, View> addReactions(final LayoutInflater layoutInflater, final ConstraintLayout container) { |
||||
|
final List<Emoji> reactions = reactionsManager.getReactions(); |
||||
|
AppCompatImageView prevSquareImageView = null; |
||||
|
View firstImageView = null; |
||||
|
View lastImageView = null; |
||||
|
for (int i = 0; i < reactions.size(); i++) { |
||||
|
final Emoji reaction = reactions.get(i); |
||||
|
final AppCompatImageView imageView = getEmojiImageView(); |
||||
|
final ConstraintLayout.LayoutParams layoutParams = (ConstraintLayout.LayoutParams) imageView.getLayoutParams(); |
||||
|
if (i == 0 && !hasOptions) { |
||||
|
// only connect bottom to parent bottom if there are no options |
||||
|
layoutParams.bottomToBottom = ConstraintLayout.LayoutParams.PARENT_ID; |
||||
|
} |
||||
|
if (i == 0) { |
||||
|
layoutParams.topToTop = ConstraintLayout.LayoutParams.PARENT_ID; |
||||
|
layoutParams.startToStart = ConstraintLayout.LayoutParams.PARENT_ID; |
||||
|
firstImageView = imageView; |
||||
|
layoutParams.setMargins(emojiMargin, emojiMargin, emojiMarginHalf, emojiMargin); |
||||
|
} else { |
||||
|
layoutParams.startToEnd = prevSquareImageView.getId(); |
||||
|
final ConstraintLayout.LayoutParams prevViewLayoutParams = (ConstraintLayout.LayoutParams) prevSquareImageView.getLayoutParams(); |
||||
|
prevViewLayoutParams.endToStart = imageView.getId(); |
||||
|
// always connect the other image view's top and bottom to the first image view top and bottom |
||||
|
layoutParams.topToTop = firstImageView.getId(); |
||||
|
layoutParams.bottomToBottom = firstImageView.getId(); |
||||
|
layoutParams.setMargins(emojiMarginHalf, emojiMargin, emojiMarginHalf, emojiMargin); |
||||
|
} |
||||
|
imageView.setImageDrawable(reaction.getDrawable()); |
||||
|
imageView.setOnClickListener(view -> { |
||||
|
if (onReactionClickListener != null) { |
||||
|
onReactionClickListener.onClick(reaction); |
||||
|
} |
||||
|
dismiss(); |
||||
|
}); |
||||
|
container.addView(imageView); |
||||
|
prevSquareImageView = imageView; |
||||
|
} |
||||
|
// add the + icon |
||||
|
if (prevSquareImageView != null) { |
||||
|
final AppCompatImageView imageView = getEmojiImageView(); |
||||
|
final ConstraintLayout.LayoutParams layoutParams = (ConstraintLayout.LayoutParams) imageView.getLayoutParams(); |
||||
|
layoutParams.topToTop = firstImageView.getId(); |
||||
|
layoutParams.bottomToBottom = firstImageView.getId(); |
||||
|
layoutParams.startToEnd = prevSquareImageView.getId(); |
||||
|
final ConstraintLayout.LayoutParams prevViewLayoutParams = (ConstraintLayout.LayoutParams) prevSquareImageView.getLayoutParams(); |
||||
|
prevViewLayoutParams.endToStart = imageView.getId(); |
||||
|
layoutParams.endToEnd = ConstraintLayout.LayoutParams.PARENT_ID; |
||||
|
layoutParams.setMargins(emojiMarginHalf - addAdjust, emojiMargin - addAdjust, emojiMargin - addAdjust, emojiMargin - addAdjust); |
||||
|
imageView.setImageResource(R.drawable.ic_add); |
||||
|
imageView.setOnClickListener(view -> { |
||||
|
if (onAddReactionListener != null) { |
||||
|
onAddReactionListener.onAdd(); |
||||
|
} |
||||
|
dismiss(); |
||||
|
}); |
||||
|
lastImageView = imageView; |
||||
|
container.addView(imageView); |
||||
|
} |
||||
|
return new Pair<>(firstImageView, lastImageView); |
||||
|
} |
||||
|
|
||||
|
@NonNull |
||||
|
private AppCompatImageView getEmojiImageView() { |
||||
|
final AppCompatImageView imageView = new AppCompatImageView(context); |
||||
|
final ConstraintLayout.LayoutParams layoutParams = new ConstraintLayout.LayoutParams(emojiSize, emojiSize); |
||||
|
imageView.setBackgroundResource(selectableItemBackgroundBorderless.resourceId); |
||||
|
imageView.setScaleType(ImageView.ScaleType.FIT_CENTER); |
||||
|
imageView.setId(SquareImageView.generateViewId()); |
||||
|
imageView.setLayoutParams(layoutParams); |
||||
|
return imageView; |
||||
|
} |
||||
|
|
||||
|
private void addOptions(final LayoutInflater layoutInflater, |
||||
|
final ConstraintLayout container, |
||||
|
@Nullable final View divider) { |
||||
|
View prevOptionView = null; |
||||
|
for (int i = 0; i < options.size(); i++) { |
||||
|
final MenuItem menuItem = options.get(i); |
||||
|
final AppCompatTextView textView = getTextView(); |
||||
|
final ConstraintLayout.LayoutParams layoutParams = (ConstraintLayout.LayoutParams) textView.getLayoutParams(); |
||||
|
layoutParams.startToStart = ConstraintLayout.LayoutParams.PARENT_ID; |
||||
|
layoutParams.endToEnd = ConstraintLayout.LayoutParams.PARENT_ID; |
||||
|
if (i == 0) { |
||||
|
if (divider != null) { |
||||
|
layoutParams.topToBottom = divider.getId(); |
||||
|
} else { |
||||
|
// if divider is null mean reactions were not added, so connect top to top of parent |
||||
|
layoutParams.topToTop = ConstraintLayout.LayoutParams.PARENT_ID; |
||||
|
} |
||||
|
((ConstraintLayout.LayoutParams) divider.getLayoutParams()).bottomToTop = textView.getId(); |
||||
|
} else { |
||||
|
layoutParams.topToBottom = prevOptionView.getId(); |
||||
|
final ConstraintLayout.LayoutParams prevLayoutParams = (ConstraintLayout.LayoutParams) prevOptionView.getLayoutParams(); |
||||
|
prevLayoutParams.bottomToTop = textView.getId(); |
||||
|
} |
||||
|
if (i == options.size() - 1) { |
||||
|
layoutParams.bottomToBottom = ConstraintLayout.LayoutParams.PARENT_ID; |
||||
|
layoutParams.bottomMargin = emojiMargin; // material design spec (https://material.io/components/menus#specs) |
||||
|
} |
||||
|
textView.setText(context.getString(menuItem.getTitleRes())); |
||||
|
textView.setOnClickListener(v -> { |
||||
|
if (onOptionSelectListener != null) { |
||||
|
onOptionSelectListener.onSelect(menuItem.getItemId()); |
||||
|
} |
||||
|
dismiss(); |
||||
|
}); |
||||
|
container.addView(textView); |
||||
|
prevOptionView = textView; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private AppCompatTextView getTextView() { |
||||
|
final AppCompatTextView textView = new AppCompatTextView(context); |
||||
|
textView.setId(AppCompatEditText.generateViewId()); |
||||
|
textView.setBackgroundResource(selectableItemBackground.resourceId); |
||||
|
textView.setGravity(Gravity.CENTER_VERTICAL); |
||||
|
textView.setPaddingRelative(optionPadding, 0, optionPadding, 0); |
||||
|
textView.setTextAppearance(context, R.style.TextAppearance_MaterialComponents_Body1); |
||||
|
final ConstraintLayout.LayoutParams layoutParams = new ConstraintLayout.LayoutParams(ConstraintLayout.LayoutParams.MATCH_CONSTRAINT, |
||||
|
optionHeight); |
||||
|
textView.setLayoutParams(layoutParams); |
||||
|
return textView; |
||||
|
} |
||||
|
|
||||
|
private View addDivider(final ConstraintLayout container, |
||||
|
final int topViewId, |
||||
|
final int startViewId, |
||||
|
final int endViewId) { |
||||
|
final View dividerView = new View(context); |
||||
|
dividerView.setId(View.generateViewId()); |
||||
|
dividerView.setBackgroundResource(R.drawable.pref_list_divider_material); |
||||
|
final ConstraintLayout.LayoutParams layoutParams = new ConstraintLayout.LayoutParams(ConstraintLayout.LayoutParams.MATCH_CONSTRAINT, |
||||
|
dividerHeight); |
||||
|
layoutParams.topToBottom = topViewId; |
||||
|
layoutParams.startToStart = startViewId; |
||||
|
layoutParams.endToEnd = endViewId; |
||||
|
dividerView.setLayoutParams(layoutParams); |
||||
|
container.addView(dividerView); |
||||
|
return dividerView; |
||||
|
} |
||||
|
|
||||
|
@NonNull |
||||
|
private Point locationOnScreen(@NonNull final View view) { |
||||
|
final int[] location = new int[2]; |
||||
|
view.getLocationOnScreen(location); |
||||
|
return new Point(location[0], location[1]); |
||||
|
} |
||||
|
|
||||
|
public static class MenuItem { |
||||
|
@IdRes |
||||
|
private final int itemId; |
||||
|
@StringRes |
||||
|
private final int titleRes; |
||||
|
|
||||
|
public MenuItem(@IdRes final int itemId, @StringRes final int titleRes) { |
||||
|
this.itemId = itemId; |
||||
|
this.titleRes = titleRes; |
||||
|
} |
||||
|
|
||||
|
public int getItemId() { |
||||
|
return itemId; |
||||
|
} |
||||
|
|
||||
|
public int getTitleRes() { |
||||
|
return titleRes; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public interface OnOptionSelectListener { |
||||
|
void onSelect(int itemId); |
||||
|
} |
||||
|
|
||||
|
public interface OnReactionClickListener { |
||||
|
void onClick(Emoji emoji); |
||||
|
} |
||||
|
|
||||
|
public interface OnAddReactionClickListener { |
||||
|
void onAdd(); |
||||
|
} |
||||
|
|
||||
|
// @NonNull |
||||
|
// private Rect getGlobalVisibleRect(@NonNull final View view) { |
||||
|
// final Rect rect = new Rect(); |
||||
|
// view.getGlobalVisibleRect(rect); |
||||
|
// return rect; |
||||
|
// } |
||||
|
|
||||
|
// private void fixPopupLocation(@NonNull final PopupWindow popupWindow, @NonNull final Point desiredLocation) { |
||||
|
// popupWindow.getContentView().post(() -> { |
||||
|
// final Point actualLocation = locationOnScreen(popupWindow.getContentView()); |
||||
|
// |
||||
|
// if (!(actualLocation.x == desiredLocation.x && actualLocation.y == desiredLocation.y)) { |
||||
|
// final int differenceX = actualLocation.x - desiredLocation.x; |
||||
|
// final int differenceY = actualLocation.y - desiredLocation.y; |
||||
|
// |
||||
|
// final int fixedOffsetX; |
||||
|
// final int fixedOffsetY; |
||||
|
// |
||||
|
// if (actualLocation.x > desiredLocation.x) { |
||||
|
// fixedOffsetX = desiredLocation.x - differenceX; |
||||
|
// } else { |
||||
|
// fixedOffsetX = desiredLocation.x + differenceX; |
||||
|
// } |
||||
|
// |
||||
|
// if (actualLocation.y > desiredLocation.y) { |
||||
|
// fixedOffsetY = desiredLocation.y - differenceY; |
||||
|
// } else { |
||||
|
// fixedOffsetY = desiredLocation.y + differenceY; |
||||
|
// } |
||||
|
// |
||||
|
// popupWindow.update(fixedOffsetX, fixedOffsetY, DO_NOT_UPDATE_FLAG, DO_NOT_UPDATE_FLAG); |
||||
|
// } |
||||
|
// }); |
||||
|
// } |
||||
|
} |
||||
|
|
@ -0,0 +1,110 @@ |
|||||
|
package awais.instagrabber.customviews; |
||||
|
|
||||
|
import android.content.Context; |
||||
|
import android.os.Handler; |
||||
|
import android.util.AttributeSet; |
||||
|
import android.view.MotionEvent; |
||||
|
import android.view.View; |
||||
|
import android.view.ViewConfiguration; |
||||
|
import android.widget.FrameLayout; |
||||
|
|
||||
|
import androidx.annotation.NonNull; |
||||
|
import androidx.annotation.Nullable; |
||||
|
|
||||
|
public class DirectItemFrameLayout extends FrameLayout { |
||||
|
private static final String TAG = DirectItemFrameLayout.class.getSimpleName(); |
||||
|
|
||||
|
private boolean longPressed = false; |
||||
|
private float touchX; |
||||
|
private float touchY; |
||||
|
private OnItemLongClickListener onItemLongClickListener; |
||||
|
private int touchSlop; |
||||
|
|
||||
|
private final Handler handler = new Handler(); |
||||
|
private final Runnable longPressRunnable = () -> { |
||||
|
longPressed = true; |
||||
|
if (onItemLongClickListener != null) { |
||||
|
onItemLongClickListener.onLongClick(this, touchX, touchY); |
||||
|
} |
||||
|
}; |
||||
|
private final Runnable longPressStartRunnable = () -> { |
||||
|
if (onItemLongClickListener != null) { |
||||
|
onItemLongClickListener.onLongClickStart(this); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
public DirectItemFrameLayout(@NonNull final Context context) { |
||||
|
super(context); |
||||
|
init(context); |
||||
|
} |
||||
|
|
||||
|
public DirectItemFrameLayout(@NonNull final Context context, @Nullable final AttributeSet attrs) { |
||||
|
super(context, attrs); |
||||
|
init(context); |
||||
|
} |
||||
|
|
||||
|
public DirectItemFrameLayout(@NonNull final Context context, @Nullable final AttributeSet attrs, final int defStyleAttr) { |
||||
|
super(context, attrs, defStyleAttr); |
||||
|
init(context); |
||||
|
} |
||||
|
|
||||
|
public DirectItemFrameLayout(@NonNull final Context context, @Nullable final AttributeSet attrs, final int defStyleAttr, final int defStyleRes) { |
||||
|
super(context, attrs, defStyleAttr, defStyleRes); |
||||
|
init(context); |
||||
|
} |
||||
|
|
||||
|
private void init(final Context context) { |
||||
|
ViewConfiguration vc = ViewConfiguration.get(context); |
||||
|
touchSlop = vc.getScaledTouchSlop(); |
||||
|
} |
||||
|
|
||||
|
public void setOnItemLongClickListener(final OnItemLongClickListener onItemLongClickListener) { |
||||
|
this.onItemLongClickListener = onItemLongClickListener; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public boolean dispatchTouchEvent(final MotionEvent ev) { |
||||
|
switch (ev.getAction()) { |
||||
|
case MotionEvent.ACTION_DOWN: |
||||
|
longPressed = false; |
||||
|
handler.postDelayed(longPressRunnable, ViewConfiguration.getLongPressTimeout()); |
||||
|
handler.postDelayed(longPressStartRunnable, ViewConfiguration.getTapTimeout()); |
||||
|
touchX = ev.getRawX(); |
||||
|
touchY = ev.getRawY(); |
||||
|
break; |
||||
|
case MotionEvent.ACTION_MOVE: |
||||
|
if (longPressed || Math.abs(touchX - ev.getRawX()) > touchSlop || Math.abs(touchY - ev.getRawY()) > touchSlop) { |
||||
|
handler.removeCallbacks(longPressStartRunnable); |
||||
|
handler.removeCallbacks(longPressRunnable); |
||||
|
} |
||||
|
break; |
||||
|
case MotionEvent.ACTION_UP: |
||||
|
handler.removeCallbacks(longPressRunnable); |
||||
|
handler.removeCallbacks(longPressStartRunnable); |
||||
|
if (longPressed) { |
||||
|
return true; |
||||
|
} |
||||
|
if (onItemLongClickListener != null) { |
||||
|
onItemLongClickListener.onLongClickCancel(this); |
||||
|
} |
||||
|
break; |
||||
|
case MotionEvent.ACTION_CANCEL: |
||||
|
handler.removeCallbacks(longPressRunnable); |
||||
|
handler.removeCallbacks(longPressStartRunnable); |
||||
|
break; |
||||
|
} |
||||
|
final boolean dispatchTouchEvent = super.dispatchTouchEvent(ev); |
||||
|
if (ev.getAction() == MotionEvent.ACTION_DOWN && !dispatchTouchEvent) { |
||||
|
return true; |
||||
|
} |
||||
|
return dispatchTouchEvent; |
||||
|
} |
||||
|
|
||||
|
public interface OnItemLongClickListener { |
||||
|
void onLongClickStart(View view); |
||||
|
|
||||
|
void onLongClickCancel(View view); |
||||
|
|
||||
|
void onLongClick(View view, float x, float y); |
||||
|
} |
||||
|
} |
@ -1,55 +0,0 @@ |
|||||
package awais.instagrabber.customviews; |
|
||||
|
|
||||
import android.app.Dialog; |
|
||||
import android.content.Context; |
|
||||
import android.graphics.drawable.Drawable; |
|
||||
import android.os.IBinder; |
|
||||
import android.view.Gravity; |
|
||||
import android.view.View; |
|
||||
import android.view.Window; |
|
||||
import android.view.WindowManager; |
|
||||
|
|
||||
import awais.instagrabber.utils.Utils; |
|
||||
|
|
||||
/** |
|
||||
* https://stackoverflow.com/a/15766097/1436766 |
|
||||
*/ |
|
||||
public class PopupDialog extends Dialog { |
|
||||
private final Context context; |
|
||||
|
|
||||
public PopupDialog(Context context) { |
|
||||
super(context); |
|
||||
this.context = context; |
|
||||
requestWindowFeature(Window.FEATURE_NO_TITLE); |
|
||||
} |
|
||||
|
|
||||
public void showAtLocation(final IBinder token, final int gravity, int x, int y) { |
|
||||
final Window window = getWindow(); |
|
||||
if (window == null) return; |
|
||||
WindowManager.LayoutParams layoutParams = window.getAttributes(); |
|
||||
layoutParams.gravity = gravity; |
|
||||
layoutParams.x = x; |
|
||||
layoutParams.y = y; |
|
||||
// layoutParams.token = token; |
|
||||
show(); |
|
||||
} |
|
||||
|
|
||||
public void showAsDropDown(View view) { |
|
||||
float density = Utils.displayMetrics.density; |
|
||||
final Window window = getWindow(); |
|
||||
if (window == null) return; |
|
||||
WindowManager.LayoutParams layoutParams = window.getAttributes(); |
|
||||
int[] location = new int[2]; |
|
||||
view.getLocationInWindow(location); |
|
||||
layoutParams.gravity = Gravity.TOP | Gravity.START; |
|
||||
layoutParams.x = location[0] + (int) (view.getWidth() / density); |
|
||||
layoutParams.y = location[1] + (int) (view.getHeight() / density); |
|
||||
show(); |
|
||||
} |
|
||||
|
|
||||
public void setBackgroundDrawable(final Drawable drawable) { |
|
||||
final Window window = getWindow(); |
|
||||
if (window == null) return; |
|
||||
window.setBackgroundDrawable(drawable); |
|
||||
} |
|
||||
} |
|
@ -0,0 +1,76 @@ |
|||||
|
package awais.instagrabber.customviews.emoji; |
||||
|
|
||||
|
import android.util.Log; |
||||
|
|
||||
|
import com.google.common.collect.ImmutableList; |
||||
|
|
||||
|
import org.json.JSONArray; |
||||
|
import org.json.JSONException; |
||||
|
|
||||
|
import java.util.ArrayList; |
||||
|
import java.util.List; |
||||
|
import java.util.Map; |
||||
|
|
||||
|
import awais.instagrabber.utils.AppExecutors; |
||||
|
import awais.instagrabber.utils.TextUtils; |
||||
|
import awais.instagrabber.utils.Utils; |
||||
|
import awais.instagrabber.utils.emoji.EmojiParser; |
||||
|
|
||||
|
import static awais.instagrabber.utils.Constants.PREF_REACTIONS; |
||||
|
|
||||
|
public class ReactionsManager { |
||||
|
private static final String TAG = ReactionsManager.class.getSimpleName(); |
||||
|
private static final Object LOCK = new Object(); |
||||
|
|
||||
|
private final AppExecutors appExecutors = AppExecutors.getInstance(); |
||||
|
private final List<Emoji> reactions = new ArrayList<>(); |
||||
|
|
||||
|
private static ReactionsManager instance; |
||||
|
|
||||
|
public static ReactionsManager getInstance() { |
||||
|
if (instance == null) { |
||||
|
synchronized (LOCK) { |
||||
|
if (instance == null) { |
||||
|
instance = new ReactionsManager(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
return instance; |
||||
|
} |
||||
|
|
||||
|
private ReactionsManager() { |
||||
|
String reactionsJson = Utils.settingsHelper.getString(PREF_REACTIONS); |
||||
|
if (TextUtils.isEmpty(reactionsJson)) { |
||||
|
final ImmutableList<String> list = ImmutableList.of("❤️", "\uD83D\uDE02", "\uD83D\uDE2E", "\uD83D\uDE22", "\uD83D\uDE21", "\uD83D\uDC4D"); |
||||
|
reactionsJson = new JSONArray(list).toString(); |
||||
|
} |
||||
|
final EmojiParser emojiParser = EmojiParser.getInstance(); |
||||
|
final Map<String, Emoji> allEmojis = emojiParser.getAllEmojis(); |
||||
|
try { |
||||
|
final JSONArray reactionsJsonArray = new JSONArray(reactionsJson); |
||||
|
for (int i = 0; i < reactionsJsonArray.length(); i++) { |
||||
|
final String emojiUnicode = reactionsJsonArray.optString(i); |
||||
|
if (emojiUnicode == null) continue; |
||||
|
final Emoji emoji = allEmojis.get(emojiUnicode); |
||||
|
if (emoji == null) continue; |
||||
|
reactions.add(emoji); |
||||
|
} |
||||
|
} catch (JSONException e) { |
||||
|
Log.e(TAG, "ReactionsManager: ", e); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public List<Emoji> getReactions() { |
||||
|
return reactions; |
||||
|
} |
||||
|
|
||||
|
// public void setVariant(final String parent, final String variant) { |
||||
|
// if (parent == null || variant == null) return; |
||||
|
// selectedVariantMap.put(parent, variant); |
||||
|
// appExecutors.tasksThread().execute(() -> { |
||||
|
// final JSONObject jsonObject = new JSONObject(selectedVariantMap); |
||||
|
// final String json = jsonObject.toString(); |
||||
|
// Utils.settingsHelper.putString(PREF_EMOJI_VARIANTS, json); |
||||
|
// }); |
||||
|
// } |
||||
|
} |
@ -0,0 +1,12 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android" |
||||
|
android:shape="rectangle"> |
||||
|
<solid android:color="?colorSurface" /> |
||||
|
<padding |
||||
|
android:bottom="4dp" |
||||
|
android:left="4dp" |
||||
|
android:right="4dp" |
||||
|
android:top="4dp" /> |
||||
|
|
||||
|
<corners android:radius="8dp" /> |
||||
|
</shape> |
@ -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="?attr/colorControlNormal" |
|
||||
android:viewportWidth="24" |
android:viewportWidth="24" |
||||
android:viewportHeight="24"> |
|
||||
<path |
|
||||
android:fillColor="@android:color/white" |
|
||||
android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z" /> |
|
||||
|
android:viewportHeight="24" |
||||
|
android:tint="?attr/colorControlNormal"> |
||||
|
<path |
||||
|
android:fillColor="@android:color/white" |
||||
|
android:pathData="M18,13h-5v5c0,0.55 -0.45,1 -1,1s-1,-0.45 -1,-1v-5H6c-0.55,0 -1,-0.45 -1,-1s0.45,-1 1,-1h5V6c0,-0.55 0.45,-1 1,-1s1,0.45 1,1v5h5c0.55,0 1,0.45 1,1s-0.45,1 -1,1z"/> |
||||
</vector> |
</vector> |
@ -1,5 +1,5 @@ |
|||||
<?xml version="1.0" encoding="utf-8"?> |
<?xml version="1.0" encoding="utf-8"?> |
||||
<View xmlns:android="http://schemas.android.com/apk/res/android" |
<View xmlns:android="http://schemas.android.com/apk/res/android" |
||||
android:layout_width="match_parent" |
android:layout_width="match_parent" |
||||
android:layout_height="1dp" |
|
||||
|
android:layout_height="@dimen/horizontal_divider_height" |
||||
android:background="@drawable/pref_list_divider_material" /> |
android:background="@drawable/pref_list_divider_material" /> |
@ -0,0 +1,69 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android" |
||||
|
xmlns:app="http://schemas.android.com/apk/res-auto" |
||||
|
android:id="@+id/card" |
||||
|
android:layout_width="wrap_content" |
||||
|
android:layout_height="wrap_content" |
||||
|
app:cardCornerRadius="8dp" |
||||
|
app:cardElevation="2dp"> |
||||
|
|
||||
|
<androidx.constraintlayout.widget.ConstraintLayout |
||||
|
android:id="@+id/container" |
||||
|
android:layout_width="wrap_content" |
||||
|
android:layout_height="wrap_content"> |
||||
|
|
||||
|
<!--<androidx.appcompat.widget.AppCompatImageView--> |
||||
|
<!-- android:id="@+id/img1"--> |
||||
|
<!-- android:layout_width="48dp"--> |
||||
|
<!-- android:layout_height="0dp"--> |
||||
|
<!-- android:padding="4dp"--> |
||||
|
<!-- android:scaleType="fitCenter"--> |
||||
|
<!-- app:layout_constraintBottom_toTopOf="@id/divider"--> |
||||
|
<!-- app:layout_constraintDimensionRatio="1"--> |
||||
|
<!-- app:layout_constraintEnd_toStartOf="@id/img2"--> |
||||
|
<!-- app:layout_constraintStart_toStartOf="parent"--> |
||||
|
<!-- app:layout_constraintTop_toTopOf="parent"--> |
||||
|
<!-- tools:srcCompat="@mipmap/ic_launcher" />--> |
||||
|
|
||||
|
<!--<androidx.appcompat.widget.AppCompatImageView--> |
||||
|
<!-- android:id="@+id/img2"--> |
||||
|
<!-- android:layout_width="48dp"--> |
||||
|
<!-- android:layout_height="0dp"--> |
||||
|
<!-- android:padding="4dp"--> |
||||
|
<!-- android:scaleType="fitCenter"--> |
||||
|
<!-- app:layout_constraintDimensionRatio="1"--> |
||||
|
<!-- app:layout_constraintEnd_toStartOf="@id/img3"--> |
||||
|
<!-- app:layout_constraintStart_toEndOf="@id/img1"--> |
||||
|
<!-- app:layout_constraintTop_toTopOf="parent"--> |
||||
|
<!-- tools:srcCompat="@mipmap/ic_launcher" />--> |
||||
|
|
||||
|
<!--<awais.instagrabber.customviews.SquareImageView--> |
||||
|
<!-- android:id="@+id/img3"--> |
||||
|
<!-- android:layout_width="48dp"--> |
||||
|
<!-- android:layout_height="48dp"--> |
||||
|
<!-- android:padding="4dp"--> |
||||
|
<!-- app:layout_constraintEnd_toEndOf="parent"--> |
||||
|
<!-- app:layout_constraintStart_toEndOf="@id/img2"--> |
||||
|
<!-- app:layout_constraintTop_toTopOf="parent"--> |
||||
|
<!-- tools:srcCompat="@mipmap/ic_launcher" />--> |
||||
|
|
||||
|
<!--<include--> |
||||
|
<!-- android:id="@+id/divider"--> |
||||
|
<!-- layout="@layout/item_pref_divider"--> |
||||
|
<!-- android:layout_width="0dp"--> |
||||
|
<!-- android:layout_height="1dp"--> |
||||
|
<!-- app:layout_constraintEnd_toEndOf="@id/img3"--> |
||||
|
<!-- app:layout_constraintStart_toStartOf="@id/img1"--> |
||||
|
<!-- app:layout_constraintTop_toBottomOf="@id/img1" />--> |
||||
|
|
||||
|
<!--<androidx.appcompat.widget.AppCompatTextView--> |
||||
|
<!-- android:layout_width="0dp"--> |
||||
|
<!-- android:layout_height="wrap_content"--> |
||||
|
<!-- android:text="test"--> |
||||
|
<!-- app:layout_constraintBottom_toBottomOf="parent"--> |
||||
|
<!-- app:layout_constraintEnd_toEndOf="parent"--> |
||||
|
<!-- app:layout_constraintStart_toStartOf="parent"--> |
||||
|
<!-- app:layout_constraintTop_toBottomOf="@id/divider" />--> |
||||
|
|
||||
|
</androidx.constraintlayout.widget.ConstraintLayout> |
||||
|
</androidx.cardview.widget.CardView> |
@ -0,0 +1,5 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<resources> |
||||
|
<item name="reply" type="id" /> |
||||
|
<item name="unsend" type="id" /> |
||||
|
</resources> |
Write
Preview
Loading…
Cancel
Save
Reference in new issue