Info sheet in media viewer (AND-109)

This commit is contained in:
Grishka
2023-12-04 21:33:25 +03:00
parent b08cd1eb4b
commit 5d7c37262e
26 changed files with 645 additions and 235 deletions

View File

@@ -9,7 +9,7 @@ android {
applicationId "org.joinmastodon.android"
minSdk 23
targetSdk 33
versionCode 80
versionCode 81
versionName "2.2.4"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
resConfigs "ar-rSA", "be-rBY", "bn-rBD", "bs-rBA", "ca-rES", "cs-rCZ", "da-rDK", "de-rDE", "el-rGR", "es-rES", "eu-rES", "fa-rIR", "fi-rFI", "fil-rPH", "fr-rFR", "ga-rIE", "gd-rGB", "gl-rES", "hi-rIN", "hr-rHR", "hu-rHU", "hy-rAM", "ig-rNG", "in-rID", "is-rIS", "it-rIT", "iw-rIL", "ja-rJP", "kab", "ko-rKR", "my-rMM", "nl-rNL", "no-rNO", "oc-rFR", "pl-rPL", "pt-rBR", "pt-rPT", "ro-rRO", "ru-rRU", "si-rLK", "sl-rSI", "sv-rSE", "th-rTH", "tr-rTR", "uk-rUA", "ur-rIN", "vi-rVN", "zh-rCN", "zh-rTW"
@@ -76,7 +76,7 @@ dependencies {
implementation 'me.grishka.litex:viewpager:1.0.0'
implementation 'me.grishka.litex:viewpager2:1.0.0'
implementation 'me.grishka.litex:palette:1.0.0'
implementation 'me.grishka.appkit:appkit:1.2.15'
implementation 'me.grishka.appkit:appkit:1.2.16'
implementation 'com.google.code.gson:gson:2.8.9'
implementation 'org.jsoup:jsoup:1.14.3'
implementation 'com.squareup:otto:1.3.8'

View File

@@ -32,7 +32,6 @@ import org.joinmastodon.android.ui.M3AlertDialogBuilder;
import org.joinmastodon.android.ui.NonMutualPreReplySheet;
import org.joinmastodon.android.ui.OldPostPreReplySheet;
import org.joinmastodon.android.ui.displayitems.AccountStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.ExtendedFooterStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.GapStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.HashtagStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.MediaGridStatusDisplayItem;
@@ -182,7 +181,7 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
@Override
public void openPhotoViewer(String parentID, Status _status, int attachmentIndex, MediaGridStatusDisplayItem.Holder gridHolder){
final Status status=_status.getContentStatus();
currentPhotoViewer=new PhotoViewer(getActivity(), status.mediaAttachments, attachmentIndex, new PhotoViewer.Listener(){
currentPhotoViewer=new PhotoViewer(getActivity(), status.mediaAttachments, attachmentIndex, status, accountID, new PhotoViewer.Listener(){
private MediaAttachmentViewController transitioningHolder;
@Override

View File

@@ -7,10 +7,7 @@ import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.media.MediaMetadataRetriever;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.text.SpannableStringBuilder;
import android.text.style.BulletSpan;
import android.util.Log;
import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
@@ -131,20 +128,9 @@ public class ComposeImageDescriptionFragment extends MastodonToolbarFragment imp
@Override
public boolean onOptionsItemSelected(MenuItem item){
if(item.getItemId()==R.id.help){
SpannableStringBuilder msg=new SpannableStringBuilder(getText(R.string.alt_text_help));
BulletSpan[] spans=msg.getSpans(0, msg.length(), BulletSpan.class);
for(BulletSpan span:spans){
BulletSpan betterSpan;
if(Build.VERSION.SDK_INT<Build.VERSION_CODES.Q)
betterSpan=new BulletSpan(V.dp(10), UiUtils.getThemeColor(themeWrapper, R.attr.colorM3OnSurface));
else
betterSpan=new BulletSpan(V.dp(10), UiUtils.getThemeColor(themeWrapper, R.attr.colorM3OnSurface), V.dp(1.5f));
msg.setSpan(betterSpan, msg.getSpanStart(span), msg.getSpanEnd(span), msg.getSpanFlags(span));
msg.removeSpan(span);
}
new M3AlertDialogBuilder(themeWrapper)
.setTitle(R.string.what_is_alt_text)
.setMessage(msg)
.setMessage(UiUtils.fixBulletListInString(themeWrapper, R.string.alt_text_help))
.setPositiveButton(R.string.ok, null)
.show();
}
@@ -181,7 +167,7 @@ public class ComposeImageDescriptionFragment extends MastodonToolbarFragment imp
fakeAttachment.meta.width=width;
fakeAttachment.meta.height=height;
photoViewer=new PhotoViewer(getActivity(), Collections.singletonList(fakeAttachment), 0, new PhotoViewer.Listener(){
photoViewer=new PhotoViewer(getActivity(), Collections.singletonList(fakeAttachment), 0, null, accountID, new PhotoViewer.Listener(){
@Override
public void setPhotoViewVisibility(int index, boolean visible){
image.setAlpha(visible ? 1f : 0f);

View File

@@ -40,7 +40,6 @@ import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.Toolbar;
import org.joinmastodon.android.GlobalUserPreferences;
@@ -977,7 +976,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
return;
int radius=V.dp(25);
currentPhotoViewer=new PhotoViewer(getActivity(), createFakeAttachments(account.avatar, ava), 0,
new SingleImagePhotoViewerListener(avatar, avatarBorder, new int[]{radius, radius, radius, radius}, this, ()->currentPhotoViewer=null, ()->ava, null, null));
null, accountID, new SingleImagePhotoViewerListener(avatar, avatarBorder, new int[]{radius, radius, radius, radius}, this, ()->currentPhotoViewer=null, ()->ava, null, null));
}
}
@@ -989,7 +988,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
if(drawable==null || drawable instanceof ColorDrawable)
return;
currentPhotoViewer=new PhotoViewer(getActivity(), createFakeAttachments(account.header, drawable), 0,
new SingleImagePhotoViewerListener(cover, cover, null, this, ()->currentPhotoViewer=null, ()->drawable, ()->avatarBorder.setTranslationZ(2), ()->avatarBorder.setTranslationZ(0)));
null, accountID, new SingleImagePhotoViewerListener(cover, cover, null, this, ()->currentPhotoViewer=null, ()->drawable, ()->avatarBorder.setTranslationZ(2), ()->avatarBorder.setTranslationZ(0)));
}
}

View File

@@ -5,7 +5,6 @@ import android.graphics.drawable.ColorDrawable;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
@@ -14,7 +13,6 @@ import org.joinmastodon.android.R;
import org.joinmastodon.android.ui.utils.UiUtils;
import androidx.annotation.NonNull;
import me.grishka.appkit.utils.V;
import me.grishka.appkit.views.BottomSheet;
public abstract class PreReplySheet extends BottomSheet{
@@ -28,8 +26,6 @@ public abstract class PreReplySheet extends BottomSheet{
View content=context.getSystemService(LayoutInflater.class).inflate(R.layout.sheet_pre_reply, null);
setContentView(content);
FrameLayout.LayoutParams lp=(FrameLayout.LayoutParams) content.getLayoutParams();
lp.topMargin=V.dp(72);
setNavigationBarBackground(new ColorDrawable(UiUtils.alphaBlendColors(UiUtils.getThemeColor(context, R.attr.colorM3Surface),
UiUtils.getThemeColor(context, R.attr.colorM3Primary), 0.05f)), !UiUtils.isDarkTheme());

View File

@@ -26,6 +26,7 @@ import org.joinmastodon.android.model.Translation;
import org.joinmastodon.android.ui.OutlineProviders;
import org.joinmastodon.android.ui.PhotoLayoutHelper;
import org.joinmastodon.android.ui.drawables.SpoilerStripesDrawable;
import org.joinmastodon.android.ui.photoviewer.AltTextSheet;
import org.joinmastodon.android.ui.photoviewer.PhotoViewerHost;
import org.joinmastodon.android.ui.utils.MediaAttachmentViewController;
import org.joinmastodon.android.ui.views.FrameLayoutThatOnlyMeasuresFirstChild;
@@ -103,11 +104,6 @@ public class MediaGridStatusDisplayItem extends StatusDisplayItem{
private final ArrayList<MediaAttachmentViewController> controllers=new ArrayList<>();
private final MaxWidthFrameLayout overlays;
private final FrameLayout altTextWrapper;
private final TextView altTextButton;
private final View altTextScroller;
private final ImageButton altTextClose;
private final TextView altText;
private final View sensitiveOverlay;
private final LayerDrawable sensitiveOverlayBG;
@@ -115,9 +111,6 @@ public class MediaGridStatusDisplayItem extends StatusDisplayItem{
private final TextView hideSensitiveButton;
private final TextView sensitiveText;
private int altTextIndex=-1;
private Animator altTextAnimator;
public Holder(Activity activity, ViewGroup parent){
super(new FrameLayoutThatOnlyMeasuresFirstChild(activity));
wrapper=(FrameLayout)itemView;
@@ -129,14 +122,6 @@ public class MediaGridStatusDisplayItem extends StatusDisplayItem{
overlays.setMaxWidth(V.dp(MediaGridLayout.MAX_WIDTH));
wrapper.addView(overlays, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, Gravity.CENTER_HORIZONTAL));
activity.getLayoutInflater().inflate(R.layout.overlay_image_alt_text, overlays);
altTextWrapper=findViewById(R.id.alt_text_wrapper);
altTextButton=findViewById(R.id.alt_button);
altTextScroller=findViewById(R.id.alt_text_scroller);
altTextClose=findViewById(R.id.alt_text_close);
altText=findViewById(R.id.alt_text);
altTextClose.setOnClickListener(this::onAltTextCloseClick);
hideSensitiveButton=(TextView) activity.getLayoutInflater().inflate(R.layout.alt_text_badge, overlays, false);
hideSensitiveButton.setText(R.string.hide);
FrameLayout.LayoutParams lp;
@@ -160,9 +145,6 @@ public class MediaGridStatusDisplayItem extends StatusDisplayItem{
public void onBind(MediaGridStatusDisplayItem item){
wrapper.setPadding(0, 0, 0, item.inset ? 0 : V.dp(8));
if(altTextAnimator!=null)
altTextAnimator.cancel();
layout.setTiledLayout(item.tiledLayout);
for(MediaAttachmentViewController c:controllers){
item.viewPool.reuse(c.type, c);
@@ -212,8 +194,6 @@ public class MediaGridStatusDisplayItem extends StatusDisplayItem{
c.bind(att, item.status);
i++;
}
altTextWrapper.setVisibility(View.GONE);
altTextIndex=-1;
if(!item.sensitiveRevealed){
sensitiveOverlay.setVisibility(View.VISIBLE);
@@ -246,115 +226,9 @@ public class MediaGridStatusDisplayItem extends StatusDisplayItem{
}
private void onAltTextClick(View v){
if(altTextAnimator!=null)
altTextAnimator.cancel();
v.setVisibility(View.INVISIBLE);
int index=(Integer)v.getTag();
altTextIndex=index;
Attachment att=item.attachments.get(index);
altText.setText(att.description);
altTextWrapper.setVisibility(View.VISIBLE);
altTextWrapper.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener(){
@Override
public boolean onPreDraw(){
altTextWrapper.getViewTreeObserver().removeOnPreDrawListener(this);
int[] loc={0, 0};
v.getLocationInWindow(loc);
int btnL=loc[0], btnT=loc[1];
overlays.getLocationInWindow(loc);
btnL-=loc[0];
btnT-=loc[1];
ArrayList<Animator> anims=new ArrayList<>();
anims.add(ObjectAnimator.ofFloat(altTextButton, View.ALPHA, 1, 0));
anims.add(ObjectAnimator.ofFloat(altTextScroller, View.ALPHA, 0, 1));
anims.add(ObjectAnimator.ofFloat(altTextClose, View.ALPHA, 0, 1));
anims.add(ObjectAnimator.ofInt(altTextWrapper, "left", btnL, altTextWrapper.getLeft()));
anims.add(ObjectAnimator.ofInt(altTextWrapper, "top", btnT, altTextWrapper.getTop()));
anims.add(ObjectAnimator.ofInt(altTextWrapper, "right", btnL+v.getWidth(), altTextWrapper.getRight()));
anims.add(ObjectAnimator.ofInt(altTextWrapper, "bottom", btnT+v.getHeight(), altTextWrapper.getBottom()));
for(Animator a:anims)
a.setDuration(300);
for(MediaAttachmentViewController c:controllers){
if(c.altButton!=null && c.altButton!=v){
anims.add(ObjectAnimator.ofFloat(c.altButton, View.ALPHA, 1, 0).setDuration(150));
}
}
AnimatorSet set=new AnimatorSet();
set.playTogether(anims);
set.setInterpolator(CubicBezierInterpolator.DEFAULT);
set.addListener(new AnimatorListenerAdapter(){
@Override
public void onAnimationEnd(Animator animation){
altTextAnimator=null;
for(MediaAttachmentViewController c:controllers){
if(c.altButton!=null){
c.altButton.setVisibility(View.INVISIBLE);
}
}
}
});
altTextAnimator=set;
set.start();
return true;
}
});
}
private void onAltTextCloseClick(View v){
if(altTextAnimator!=null)
altTextAnimator.cancel();
View btn=controllers.get(altTextIndex).altButton;
int i=0;
for(MediaAttachmentViewController c:controllers){
if(c.altButton!=null && c.altButton!=btn && !TextUtils.isEmpty(item.attachments.get(i).description))
c.altButton.setVisibility(View.VISIBLE);
i++;
}
int[] loc={0, 0};
btn.getLocationInWindow(loc);
int btnL=loc[0], btnT=loc[1];
overlays.getLocationInWindow(loc);
btnL-=loc[0];
btnT-=loc[1];
ArrayList<Animator> anims=new ArrayList<>();
anims.add(ObjectAnimator.ofFloat(altTextButton, View.ALPHA, 1));
anims.add(ObjectAnimator.ofFloat(altTextScroller, View.ALPHA, 0));
anims.add(ObjectAnimator.ofFloat(altTextClose, View.ALPHA, 0));
anims.add(ObjectAnimator.ofInt(altTextWrapper, "left", btnL));
anims.add(ObjectAnimator.ofInt(altTextWrapper, "top", btnT));
anims.add(ObjectAnimator.ofInt(altTextWrapper, "right", btnL+btn.getWidth()));
anims.add(ObjectAnimator.ofInt(altTextWrapper, "bottom", btnT+btn.getHeight()));
for(Animator a:anims)
a.setDuration(300);
for(MediaAttachmentViewController c:controllers){
if(c.altButton!=null && c.altButton!=btn){
anims.add(ObjectAnimator.ofFloat(c.altButton, View.ALPHA, 1).setDuration(150));
}
}
AnimatorSet set=new AnimatorSet();
set.playTogether(anims);
set.setInterpolator(CubicBezierInterpolator.DEFAULT);
set.addListener(new AnimatorListenerAdapter(){
@Override
public void onAnimationEnd(Animator animation){
altTextAnimator=null;
altTextWrapper.setVisibility(View.GONE);
btn.setVisibility(View.VISIBLE);
btn.setAlpha(1);
}
});
altTextAnimator=set;
set.start();
new AltTextSheet(v.getContext(), att).show();
}
public MediaAttachmentViewController getViewController(int index){

View File

@@ -0,0 +1,37 @@
package org.joinmastodon.android.ui.photoviewer;
import android.content.Context;
import android.graphics.drawable.ColorDrawable;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.TextView;
import org.joinmastodon.android.R;
import org.joinmastodon.android.model.Attachment;
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
import org.joinmastodon.android.ui.utils.UiUtils;
import androidx.annotation.NonNull;
import me.grishka.appkit.views.BottomSheet;
public class AltTextSheet extends BottomSheet{
public AltTextSheet(@NonNull Context context, Attachment attachment){
super(context);
View content=context.getSystemService(LayoutInflater.class).inflate(R.layout.sheet_alt_text, null);
setContentView(content);
TextView altText=findViewById(R.id.alt_text);
altText.setText(attachment.description);
findViewById(R.id.alt_text_help).setOnClickListener(v->showAltTextHelp());
setNavigationBarBackground(new ColorDrawable(UiUtils.alphaBlendColors(UiUtils.getThemeColor(context, R.attr.colorM3Surface),
UiUtils.getThemeColor(context, R.attr.colorM3Primary), 0.05f)), !UiUtils.isDarkTheme());
}
private void showAltTextHelp(){
new M3AlertDialogBuilder(getContext())
.setTitle(R.string.what_is_alt_text)
.setMessage(UiUtils.fixBulletListInString(getContext(), R.string.alt_text_help))
.setPositiveButton(R.string.ok, null)
.show();
}
}

View File

@@ -1,6 +1,10 @@
package org.joinmastodon.android.ui.photoviewer;
import android.Manifest;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.DownloadManager;
@@ -26,6 +30,8 @@ import android.os.SystemClock;
import android.provider.MediaStore;
import android.provider.Settings;
import android.util.Log;
import android.util.Property;
import android.view.ContextThemeWrapper;
import android.view.DisplayCutout;
import android.view.Gravity;
import android.view.KeyEvent;
@@ -48,8 +54,12 @@ import android.widget.Toolbar;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.MastodonAPIController;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
import org.joinmastodon.android.model.Attachment;
import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
import org.joinmastodon.android.ui.utils.UiUtils;
import java.io.File;
import java.io.FileOutputStream;
@@ -85,6 +95,8 @@ public class PhotoViewer implements ZoomPanView.Listener{
private int currentIndex;
private WindowManager wm;
private Listener listener;
private Status status;
private String accountID;
private FrameLayout windowView;
private FragmentRootLinearLayout uiOverlay;
@@ -104,17 +116,32 @@ public class PhotoViewer implements ZoomPanView.Listener{
if(uiVisible)
toggleUI();
};
private Animator currentSheetRelatedToolbarAnimation;
private boolean videoPositionNeedsUpdating;
private Runnable videoPositionUpdater=this::updateVideoPosition;
private int videoDuration, videoInitialPosition, videoLastTimeUpdatePosition;
private long videoInitialPositionTime;
public PhotoViewer(Activity activity, List<Attachment> attachments, int index, Listener listener){
private static final Property<FragmentRootLinearLayout, Integer> STATUS_BAR_COLOR_PROPERTY=new Property<>(Integer.class, "Fdsafdsa"){
@Override
public Integer get(FragmentRootLinearLayout object){
return object.getStatusBarColor();
}
@Override
public void set(FragmentRootLinearLayout object, Integer value){
object.setStatusBarColor(value);
}
};
public PhotoViewer(Activity activity, List<Attachment> attachments, int index, Status status, String accountID, Listener listener){
this.activity=activity;
this.attachments=attachments.stream().filter(a->a.type==Attachment.Type.IMAGE || a.type==Attachment.Type.GIFV || a.type==Attachment.Type.VIDEO).collect(Collectors.toList());
currentIndex=index;
this.listener=listener;
this.status=status;
this.accountID=accountID;
wm=activity.getWindowManager();
@@ -175,9 +202,15 @@ public class PhotoViewer implements ZoomPanView.Listener{
toolbarWrap=uiOverlay.findViewById(R.id.toolbar_wrap);
toolbar=uiOverlay.findViewById(R.id.toolbar);
toolbar.setNavigationOnClickListener(v->onStartSwipeToDismissTransition(0));
toolbar.getMenu().add(R.string.download).setIcon(R.drawable.ic_download_24px).setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
if(status!=null)
toolbar.getMenu().add(R.string.info).setIcon(R.drawable.ic_info_24px).setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
else
toolbar.getMenu().add(R.string.download).setIcon(R.drawable.ic_download_24px).setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
toolbar.setOnMenuItemClickListener(item->{
saveCurrentFile();
if(status!=null)
showInfoSheet();
else
saveCurrentFile();
return true;
});
uiOverlay.setAlpha(0f);
@@ -610,6 +643,93 @@ public class PhotoViewer implements ZoomPanView.Listener{
}
}
private void showInfoSheet(){
pauseVideo();
PhotoViewerInfoSheet sheet=new PhotoViewerInfoSheet(new ContextThemeWrapper(activity, R.style.Theme_Mastodon_Dark), attachments.get(currentIndex), toolbar.getHeight(), new PhotoViewerInfoSheet.Listener(){
private boolean ignoreBeforeDismiss;
@Override
public void onBeforeDismiss(int duration){
if(ignoreBeforeDismiss)
return;
if(currentSheetRelatedToolbarAnimation!=null)
currentSheetRelatedToolbarAnimation.cancel();
AnimatorSet set=new AnimatorSet();
set.playTogether(
ObjectAnimator.ofFloat(pager, View.TRANSLATION_Y, 0),
ObjectAnimator.ofFloat(toolbarWrap, View.ALPHA, 1f),
ObjectAnimator.ofArgb(uiOverlay, STATUS_BAR_COLOR_PROPERTY, 0x80000000)
);
set.setDuration(duration);
set.setInterpolator(CubicBezierInterpolator.EASE_OUT);
currentSheetRelatedToolbarAnimation=set;
set.addListener(new AnimatorListenerAdapter(){
@Override
public void onAnimationEnd(Animator animation){
currentSheetRelatedToolbarAnimation=null;
}
});
set.start();
}
@Override
public void onDismissEntireViewer(){
ignoreBeforeDismiss=true;
onStartSwipeToDismissTransition(0);
}
@Override
public void onButtonClick(int id){
if(id==R.id.btn_boost){
if(status!=null){
AccountSessionManager.get(accountID).getStatusInteractionController().setReblogged(status, !status.reblogged);
}
}else if(id==R.id.btn_favorite){
if(status!=null){
AccountSessionManager.get(accountID).getStatusInteractionController().setFavorited(status, !status.favourited);
}
}else if(id==R.id.btn_share){
if(status!=null){
UiUtils.openSystemShareSheet(activity, status.url);
}
}else if(id==R.id.btn_bookmark){
if(status!=null){
AccountSessionManager.get(accountID).getStatusInteractionController().setBookmarked(status, !status.bookmarked);
}
}else if(id==R.id.btn_download){
saveCurrentFile();
}
}
});
sheet.setStatus(status);
sheet.show();
if(currentSheetRelatedToolbarAnimation!=null)
currentSheetRelatedToolbarAnimation.cancel();
sheet.getWindow().getDecorView().getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener(){
@Override
public boolean onPreDraw(){
sheet.getWindow().getDecorView().getViewTreeObserver().removeOnPreDrawListener(this);
AnimatorSet set=new AnimatorSet();
set.playTogether(
ObjectAnimator.ofFloat(pager, View.TRANSLATION_Y, -pager.getHeight()*0.2f),
ObjectAnimator.ofFloat(toolbarWrap, View.ALPHA, 0f),
ObjectAnimator.ofArgb(uiOverlay, STATUS_BAR_COLOR_PROPERTY, 0)
);
set.setDuration(300);
set.setInterpolator(CubicBezierInterpolator.DEFAULT);
currentSheetRelatedToolbarAnimation=set;
set.addListener(new AnimatorListenerAdapter(){
@Override
public void onAnimationEnd(Animator animation){
currentSheetRelatedToolbarAnimation=null;
}
});
set.start();
return true;
}
});
}
public interface Listener{
void setPhotoViewVisibility(int index, boolean visible);

View File

@@ -0,0 +1,180 @@
package org.joinmastodon.android.ui.photoviewer;
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.drawable.ColorDrawable;
import android.text.TextUtils;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewOutlineProvider;
import android.view.ViewTreeObserver;
import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.ImageButton;
import android.widget.TextView;
import com.squareup.otto.Subscribe;
import org.joinmastodon.android.E;
import org.joinmastodon.android.R;
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
import org.joinmastodon.android.model.Attachment;
import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.model.StatusPrivacy;
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
import org.joinmastodon.android.ui.utils.UiUtils;
import androidx.annotation.NonNull;
import me.grishka.appkit.utils.CubicBezierInterpolator;
import me.grishka.appkit.utils.V;
import me.grishka.appkit.views.BottomSheet;
public class PhotoViewerInfoSheet extends BottomSheet{
private final Attachment attachment;
private final View buttonsContainer;
private final TextView altText;
private final ImageButton backButton, infoButton;
private final Button boostBtn, favoriteBtn, bookmarkBtn;
private final Listener listener;
private String statusID;
public PhotoViewerInfoSheet(@NonNull Context context, Attachment attachment, int toolbarHeight, Listener listener){
super(context);
this.attachment=attachment;
this.listener=listener;
dimAmount=0;
View content=context.getSystemService(LayoutInflater.class).inflate(R.layout.sheet_photo_viewer_info, null);
setContentView(content);
setNavigationBarBackground(new ColorDrawable(UiUtils.alphaBlendColors(UiUtils.getThemeColor(context, R.attr.colorM3Surface),
UiUtils.getThemeColor(context, R.attr.colorM3Primary), 0.05f)), !UiUtils.isDarkTheme());
buttonsContainer=findViewById(R.id.buttons_container);
altText=findViewById(R.id.alt_text);
if(TextUtils.isEmpty(attachment.description)){
findViewById(R.id.alt_text).setVisibility(View.GONE);
findViewById(R.id.alt_text_title).setVisibility(View.GONE);
findViewById(R.id.divider).setVisibility(View.GONE);
}else{
altText.setText(attachment.description);
findViewById(R.id.alt_text_help).setOnClickListener(v->showAltTextHelp());
}
backButton=new ImageButton(context);
backButton.setImageResource(R.drawable.ic_arrow_back);
backButton.setImageTintList(ColorStateList.valueOf(UiUtils.getThemeColor(context, R.attr.colorM3OnSurfaceVariant)));
backButton.setBackgroundResource(R.drawable.bg_button_m3_tonal_icon);
backButton.setOutlineProvider(ViewOutlineProvider.BACKGROUND);
backButton.setElevation(V.dp(2));
backButton.setAlpha(0f);
backButton.setOnClickListener(v->{
listener.onDismissEntireViewer();
dismiss();
});
infoButton=new ImageButton(context);
infoButton.setImageResource(R.drawable.ic_info_fill1_24px);
infoButton.setImageTintList(ColorStateList.valueOf(UiUtils.getThemeColor(context, R.attr.colorM3OnPrimary)));
infoButton.setBackgroundResource(R.drawable.bg_button_m3_filled_icon);
infoButton.setOutlineProvider(ViewOutlineProvider.BACKGROUND);
infoButton.setElevation(V.dp(2));
infoButton.setAlpha(0f);
infoButton.setSelected(true);
infoButton.setOnClickListener(v->dismiss());
FrameLayout.LayoutParams lp=new FrameLayout.LayoutParams(V.dp(48), V.dp(48));
lp.topMargin=toolbarHeight/2-V.dp(24);
lp.leftMargin=lp.rightMargin=V.dp(4);
lp.gravity=Gravity.START | Gravity.TOP;
container.addView(backButton, lp);
lp=new FrameLayout.LayoutParams(lp);
lp.leftMargin=lp.rightMargin=0;
lp.gravity=Gravity.END | Gravity.TOP;
container.addView(infoButton, lp);
boostBtn=findViewById(R.id.btn_boost);
favoriteBtn=findViewById(R.id.btn_favorite);
bookmarkBtn=findViewById(R.id.btn_bookmark);
View.OnClickListener clickListener=v->listener.onButtonClick(v.getId());
boostBtn.setOnClickListener(clickListener);
favoriteBtn.setOnClickListener(clickListener);
findViewById(R.id.btn_share).setOnClickListener(clickListener);
bookmarkBtn.setOnClickListener(clickListener);
findViewById(R.id.btn_download).setOnClickListener(clickListener);
}
private void showAltTextHelp(){
new M3AlertDialogBuilder(getContext())
.setTitle(R.string.what_is_alt_text)
.setMessage(UiUtils.fixBulletListInString(getContext(), R.string.alt_text_help))
.setPositiveButton(R.string.ok, null)
.show();
}
@Override
public void dismiss(){
if(dismissed)
return;
int height=content.getHeight();
int duration=Math.max(60, (int) (180 * (height - content.getTranslationY()) / (float) height));
listener.onBeforeDismiss(duration);
backButton.animate().alpha(0).setDuration(duration).setInterpolator(CubicBezierInterpolator.EASE_OUT).start();
infoButton.animate().alpha(0).setDuration(duration).setInterpolator(CubicBezierInterpolator.EASE_OUT).start();
super.dismiss();
E.unregister(this);
}
@Override
public void show(){
super.show();
E.register(this);
content.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener(){
@Override
public boolean onPreDraw(){
content.getViewTreeObserver().removeOnPreDrawListener(this);
backButton.animate().alpha(1).setDuration(300).setInterpolator(CubicBezierInterpolator.DEFAULT).start();
infoButton.animate().alpha(1).setDuration(300).setInterpolator(CubicBezierInterpolator.DEFAULT).start();
return true;
}
});
}
public void setStatus(Status status){
statusID=status.id;
boostBtn.setCompoundDrawablesWithIntrinsicBounds(0, switch(status.visibility){
case DIRECT -> R.drawable.ic_boost_disabled_24px;
case PUBLIC, UNLISTED -> R.drawable.ic_boost;
case PRIVATE -> R.drawable.ic_boost_private;
}, 0, 0);
boostBtn.setEnabled(status.visibility!=StatusPrivacy.DIRECT);
setButtonStates(status.reblogged, status.favourited, status.bookmarked);
}
@Subscribe
public void onCountersUpdated(StatusCountersUpdatedEvent ev){
if(ev.id.equals(statusID)){
setButtonStates(ev.reblogged, ev.favorited, ev.bookmarked);
}
}
private void setButtonStates(boolean reblogged, boolean favorited, boolean bookmarked){
boostBtn.setText(reblogged ? R.string.button_reblogged : R.string.button_reblog);
boostBtn.setSelected(reblogged);
favoriteBtn.setText(favorited ? R.string.button_favorited : R.string.button_favorite);
favoriteBtn.setSelected(favorited);
bookmarkBtn.setText(bookmarked ? R.string.bookmarked : R.string.add_bookmark);
bookmarkBtn.setSelected(bookmarked);
}
public interface Listener{
void onBeforeDismiss(int duration);
void onDismissEntireViewer();
void onButtonClick(int id);
}
}

View File

@@ -26,6 +26,7 @@ import android.provider.OpenableColumns;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.TextUtils;
import android.text.style.BulletSpan;
import android.transition.ChangeBounds;
import android.transition.ChangeScroll;
import android.transition.Fade;
@@ -866,4 +867,19 @@ public class UiUtils{
lp.setMarginEnd(V.dp(marginEnd));
return lp;
}
public static CharSequence fixBulletListInString(Context context, @StringRes int res){
SpannableStringBuilder msg=new SpannableStringBuilder(context.getText(res));
BulletSpan[] spans=msg.getSpans(0, msg.length(), BulletSpan.class);
for(BulletSpan span:spans){
BulletSpan betterSpan;
if(Build.VERSION.SDK_INT<Build.VERSION_CODES.Q)
betterSpan=new BulletSpan(V.dp(10), UiUtils.getThemeColor(context, R.attr.colorM3OnSurface));
else
betterSpan=new BulletSpan(V.dp(10), UiUtils.getThemeColor(context, R.attr.colorM3OnSurface), V.dp(1.5f));
msg.setSpan(betterSpan, msg.getSpanStart(span), msg.getSpanEnd(span), msg.getSpanFlags(span));
msg.removeSpan(span);
}
return msg;
}
}

View File

@@ -1,15 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape>
<item android:bottom="-28dp">
<shape android:tint="@color/m3_primary_alpha5" android:tintMode="src_over">
<solid android:color="?colorM3Surface"/>
<corners android:topLeftRadius="28dp" android:topRightRadius="28dp"/>
</shape>
</item>
<item>
<shape android:tint="?colorM3Primary">
<solid android:color="#0D000000"/>
<corners android:topLeftRadius="28dp" android:topRightRadius="28dp"/>
<corners android:radius="28dp"/>
</shape>
</item>
</layer-list>

View File

@@ -5,7 +5,7 @@
<item>
<shape>
<solid android:color="?colorM3Primary"/>
<corners android:radius="20dp"/>
<corners android:radius="100dp"/>
</shape>
</item>
</ripple>
@@ -13,7 +13,7 @@
<item>
<shape>
<solid android:color="?colorM3DisabledBackground"/>
<corners android:radius="20dp"/>
<corners android:radius="100dp"/>
</shape>
</item>
</selector>

View File

@@ -0,0 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<inset xmlns:android="http://schemas.android.com/apk/res/android" android:drawable="@drawable/bg_button_m3_filled" android:inset="4dp">
</inset>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<ripple android:color="@color/m3_primary_overlay" xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@android:id/mask">
<shape>
<solid android:color="#000"/>
<corners android:radius="8dp"/>
</shape>
</item>
</ripple>

View File

@@ -5,7 +5,7 @@
<item>
<shape>
<solid android:color="?colorM3SecondaryContainer"/>
<corners android:radius="20dp"/>
<corners android:radius="100dp"/>
</shape>
</item>
</ripple>
@@ -13,7 +13,7 @@
<item>
<shape>
<solid android:color="?colorM3DisabledBackground"/>
<corners android:radius="20dp"/>
<corners android:radius="100dp"/>
</shape>
</item>
</selector>

View File

@@ -0,0 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<inset xmlns:android="http://schemas.android.com/apk/res/android" android:drawable="@drawable/bg_button_m3_tonal" android:inset="4dp">
</inset>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/ic_bookmark_fill1_24px" android:state_selected="true"/>
<item android:drawable="@drawable/ic_bookmark_24px"/>
</selector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M5,21V5Q5,4.175 5.588,3.587Q6.175,3 7,3H17Q17.825,3 18.413,3.587Q19,4.175 19,5V21L12,18Z"/>
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M11,17H13V11H11ZM12,9Q12.425,9 12.713,8.712Q13,8.425 13,8Q13,7.575 12.713,7.287Q12.425,7 12,7Q11.575,7 11.288,7.287Q11,7.575 11,8Q11,8.425 11.288,8.712Q11.575,9 12,9ZM12,22Q9.925,22 8.1,21.212Q6.275,20.425 4.925,19.075Q3.575,17.725 2.788,15.9Q2,14.075 2,12Q2,9.925 2.788,8.1Q3.575,6.275 4.925,4.925Q6.275,3.575 8.1,2.787Q9.925,2 12,2Q14.075,2 15.9,2.787Q17.725,3.575 19.075,4.925Q20.425,6.275 21.212,8.1Q22,9.925 22,12Q22,14.075 21.212,15.9Q20.425,17.725 19.075,19.075Q17.725,20.425 15.9,21.212Q14.075,22 12,22Z"/>
</vector>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/ic_star_fill1_24px" android:state_selected="true"/>
<item android:drawable="@drawable/ic_star_24px"/>
</selector>

View File

@@ -1,63 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/alt_text_wrapper"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="start|bottom"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:importantForAccessibility="noHideDescendants"
android:background="@drawable/bg_image_alt_overlay">
<TextView
android:id="@+id/alt_button"
android:layout_width="wrap_content"
android:layout_height="24dp"
android:paddingLeft="8dp"
android:paddingRight="8dp"
android:textAppearance="@style/m3_label_large"
android:textColor="#FFF"
android:gravity="center"
android:includeFontPadding="false"
android:text="ALT"
tools:ignore="HardcodedText" />
<ImageButton
android:id="@+id/alt_text_close"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_gravity="end|top"
android:src="@drawable/ic_baseline_close_24"
android:tint="#FFF"
android:background="?android:selectableItemBackgroundBorderless"/>
<org.joinmastodon.android.ui.views.NestableScrollView
android:id="@+id/alt_text_scroller"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="40dp"
android:requiresFadingEdge="vertical"
android:fadingEdgeLength="16dp">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/alt_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:textAppearance="@style/m3_body_medium"
android:textColor="#FFF"
tools:text="Alt text goes here"/>
</LinearLayout>
</org.joinmastodon.android.ui.views.NestableScrollView>
</FrameLayout>

View File

@@ -0,0 +1,63 @@
<?xml version="1.0" encoding="utf-8"?>
<org.joinmastodon.android.ui.views.CustomScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/bg_bottom_sheet"
android:outlineProvider="background"
android:elevation="1dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingBottom="16dp">
<View
android:id="@+id/handle"
android:layout_width="match_parent"
android:layout_height="36dp"
android:background="@drawable/bg_bottom_sheet_handle"/>
<LinearLayout
android:id="@+id/alt_text_title"
android:layout_width="match_parent"
android:layout_height="48dp"
android:orientation="horizontal"
android:layout_marginHorizontal="16dp">
<TextView
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:layout_marginEnd="8dp"
android:singleLine="true"
android:ellipsize="end"
android:textSize="22dp"
android:textColor="?colorM3OnSurfaceVariant"
android:gravity="center_vertical|start"
android:text="@string/alt_text"/>
<ImageButton
android:id="@+id/alt_text_help"
android:layout_width="48dp"
android:layout_height="48dp"
android:background="?android:actionBarItemBackground"
android:tint="?colorM3OnSurfaceVariant"
android:contentDescription="@string/help"
android:src="@drawable/ic_help_24px"/>
</LinearLayout>
<TextView
android:id="@+id/alt_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:textAppearance="@style/m3_body_large"
android:textColor="?colorM3OnSurface"
tools:text="A cute black cat"/>
</LinearLayout>
</org.joinmastodon.android.ui.views.CustomScrollView>

View File

@@ -0,0 +1,148 @@
<?xml version="1.0" encoding="utf-8"?>
<org.joinmastodon.android.ui.views.CustomScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/bg_bottom_sheet"
android:outlineProvider="background"
android:elevation="1dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingBottom="16dp">
<View
android:id="@+id/handle"
android:layout_width="match_parent"
android:layout_height="36dp"
android:background="@drawable/bg_bottom_sheet_handle"/>
<HorizontalScrollView
android:id="@+id/buttons_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fillViewport="true">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="64dp"
android:orientation="horizontal"
android:paddingHorizontal="16dp"
android:baselineAligned="false">
<Button
android:id="@+id/btn_boost"
android:layout_width="64dp"
android:layout_height="64dp"
android:text="@string/button_reblog"
android:drawableTop="@drawable/ic_boost"
style="@style/Widget.Mastodon.M3.Button.IconWithLabel"/>
<Space
android:layout_width="0dp"
android:layout_height="1dp"
android:layout_weight="1"/>
<Button
android:id="@+id/btn_favorite"
android:layout_width="64dp"
android:layout_height="64dp"
android:text="@string/button_favorite"
android:drawableTop="@drawable/ic_star_24px_selector"
style="@style/Widget.Mastodon.M3.Button.IconWithLabel"/>
<Space
android:layout_width="0dp"
android:layout_height="1dp"
android:layout_weight="1"/>
<Button
android:id="@+id/btn_share"
android:layout_width="64dp"
android:layout_height="64dp"
android:text="@string/button_share"
android:drawableTop="@drawable/ic_share_24px"
style="@style/Widget.Mastodon.M3.Button.IconWithLabel"/>
<Space
android:layout_width="0dp"
android:layout_height="1dp"
android:layout_weight="1"/>
<Button
android:id="@+id/btn_bookmark"
android:layout_width="64dp"
android:layout_height="64dp"
android:text="@string/add_bookmark"
android:drawableTop="@drawable/ic_bookmark_24px_selector"
style="@style/Widget.Mastodon.M3.Button.IconWithLabel"/>
<Space
android:layout_width="0dp"
android:layout_height="1dp"
android:layout_weight="1"/>
<Button
android:id="@+id/btn_download"
android:layout_width="64dp"
android:layout_height="64dp"
android:text="@string/download"
android:drawableTop="@drawable/ic_download_24px"
style="@style/Widget.Mastodon.M3.Button.IconWithLabel"/>
</LinearLayout>
</HorizontalScrollView>
<View
android:id="@+id/divider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginVertical="4dp"
android:layout_marginHorizontal="16dp"
android:background="?colorM3OutlineVariant"/>
<LinearLayout
android:id="@+id/alt_text_title"
android:layout_width="match_parent"
android:layout_height="48dp"
android:orientation="horizontal"
android:layout_marginHorizontal="16dp">
<TextView
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:layout_marginEnd="8dp"
android:singleLine="true"
android:ellipsize="end"
android:textSize="22dp"
android:textColor="?colorM3OnSurfaceVariant"
android:gravity="center_vertical|start"
android:text="@string/alt_text"/>
<ImageButton
android:id="@+id/alt_text_help"
android:layout_width="48dp"
android:layout_height="48dp"
android:background="?android:actionBarItemBackground"
android:tint="?colorM3OnSurfaceVariant"
android:contentDescription="@string/help"
android:src="@drawable/ic_help_24px"/>
</LinearLayout>
<TextView
android:id="@+id/alt_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:textAppearance="@style/m3_body_large"
android:textColor="?colorM3OnSurface"
tools:text="A cute black cat"/>
</LinearLayout>
</org.joinmastodon.android.ui.views.CustomScrollView>

View File

@@ -3,7 +3,9 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/bg_bottom_sheet">
android:background="@drawable/bg_bottom_sheet"
android:outlineProvider="background"
android:elevation="1dp">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"

View File

@@ -666,4 +666,8 @@
<string name="onboarding_recommendations_intro">You curate your own home feed.The more people you follow, the more active and interesting it will be.</string>
<string name="onboarding_recommendations_title">Personalize your home feed</string>
<string name="article_by_author">By %s</string>
<string name="info">Info</string>
<string name="button_reblogged">Boosted</string>
<string name="button_favorited">Favorited</string>
<string name="bookmarked">Bookmarked</string>
</resources>

View File

@@ -297,6 +297,18 @@
<item name="android:elevation">1dp</item>
</style>
<style name="Widget.Mastodon.M3.Button.IconWithLabel">
<item name="android:background">@drawable/bg_button_m3_icon_label</item>
<item name="android:textColor">@color/button_text_m3_text</item>
<item name="android:drawableTint">@color/button_text_m3_text</item>
<item name="android:drawablePadding">4dp</item>
<item name="android:textSize">11dp</item>
<item name="android:paddingLeft">0dp</item>
<item name="android:paddingRight">0dp</item>
<item name="android:paddingTop">11dp</item>
<item name="android:paddingBottom">11dp</item>
</style>
<style name="Widget.Mastodon.M3.Button.Outlined">
<item name="android:background">@drawable/bg_button_m3_outlined</item>
<item name="android:textColor">@color/button_text_m3_text</item>