Media viewer redesign (AND-196)

This commit is contained in:
Grishka 2024-10-28 11:26:40 +03:00
parent 2ad50cd972
commit 3dcc6d0013
18 changed files with 673 additions and 372 deletions

View File

@ -186,7 +186,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, status, accountID, new PhotoViewer.Listener(){
currentPhotoViewer=new PhotoViewer(getActivity(), this, status.mediaAttachments, attachmentIndex, status, accountID, new PhotoViewer.Listener(){
private MediaAttachmentViewController transitioningHolder;
@Override

View File

@ -166,7 +166,7 @@ public class ComposeImageDescriptionFragment extends MastodonToolbarFragment{
fakeAttachment.meta.width=width;
fakeAttachment.meta.height=height;
photoViewer=new PhotoViewer(getActivity(), Collections.singletonList(fakeAttachment), 0, null, accountID, new PhotoViewer.Listener(){
photoViewer=new PhotoViewer(getActivity(), null, Collections.singletonList(fakeAttachment), 0, null, accountID, new PhotoViewer.Listener(){
@Override
public void setPhotoViewVisibility(int index, boolean visible){
image.setAlpha(visible ? 1f : 0f);

View File

@ -1134,7 +1134,7 @@ public class ProfileFragment extends LoaderFragment implements ScrollableToTop{
if(ava==null)
return;
int radius=V.dp(25);
currentPhotoViewer=new PhotoViewer(getActivity(), createFakeAttachments(account.avatar, ava), 0,
currentPhotoViewer=new PhotoViewer(getActivity(), null, createFakeAttachments(account.avatar, ava), 0,
null, accountID, new SingleImagePhotoViewerListener(avatar, avatarBorder, new int[]{radius, radius, radius, radius}, this, ()->currentPhotoViewer=null, ()->ava, null, null));
}
}
@ -1148,7 +1148,7 @@ public class ProfileFragment extends LoaderFragment implements ScrollableToTop{
Drawable drawable=cover.getDrawable();
if(drawable==null || drawable instanceof ColorDrawable || account.headerStatic.endsWith("/missing.png"))
return;
currentPhotoViewer=new PhotoViewer(getActivity(), createFakeAttachments(account.header, drawable), 0,
currentPhotoViewer=new PhotoViewer(getActivity(), null, createFakeAttachments(account.header, drawable), 0,
null, accountID, new SingleImagePhotoViewerListener(cover, cover, null, this, ()->currentPhotoViewer=null, ()->drawable, ()->avatarBorder.setTranslationZ(2), ()->avatarBorder.setTranslationZ(0)));
}
}

View File

@ -0,0 +1,68 @@
package org.joinmastodon.android.ui.drawables;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PixelFormat;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import me.grishka.appkit.utils.V;
public class VideoPlayerSeekBarThumbDrawable extends Drawable{
private Paint thumbPaint=new Paint(Paint.ANTI_ALIAS_FLAG), clearPaint=new Paint(Paint.ANTI_ALIAS_FLAG);
private Path clearPath=new Path();
public VideoPlayerSeekBarThumbDrawable(){
thumbPaint.setColor(0xffffffff);
clearPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
clearPath.addRect(0, 0, V.dp(20), V.dp(32), Path.Direction.CW);
Path tmp=new Path();
float radius=V.dp(2);
tmp.addRoundRect(V.dp(-2), V.dp(12), V.dp(2), V.dp(20), radius, radius, Path.Direction.CW);
tmp.addRoundRect(V.dp(18), V.dp(12), V.dp(22), V.dp(20), radius, radius, Path.Direction.CW);
clearPath.op(tmp, Path.Op.DIFFERENCE);
}
@Override
public void draw(@NonNull Canvas canvas){
Rect bounds=getBounds();
int thumbWidth=V.dp(4), thumbHeight=V.dp(32);
int thumbX=bounds.centerX()-thumbWidth/2, thumbY=bounds.centerY()-thumbHeight/2;
canvas.save();
canvas.translate(thumbX-V.dp(8), thumbY);
canvas.drawPath(clearPath, clearPaint);
canvas.restore();
canvas.drawRoundRect(thumbX, thumbY, thumbX+thumbWidth, thumbY+thumbHeight, V.dp(2), V.dp(2), thumbPaint);
}
@Override
public void setAlpha(int alpha){
}
@Override
public void setColorFilter(@Nullable ColorFilter colorFilter){
}
@Override
public int getOpacity(){
return PixelFormat.TRANSLUCENT;
}
@Override
public int getIntrinsicWidth(){
return V.dp(8);
}
@Override
public int getIntrinsicHeight(){
return V.dp(32);
}
}

View File

@ -17,7 +17,10 @@ import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.res.ColorStateList;
import android.graphics.Insets;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.SurfaceTexture;
import android.graphics.drawable.BitmapDrawable;
@ -28,17 +31,18 @@ import android.media.MediaPlayer;
import android.media.MediaScannerConnection;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.SystemClock;
import android.provider.MediaStore;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;
import android.util.Property;
import android.view.ContextThemeWrapper;
import android.view.DisplayCutout;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.MenuItem;
import android.view.Surface;
import android.view.TextureView;
import android.view.View;
@ -53,18 +57,27 @@ import android.widget.ProgressBar;
import android.widget.SeekBar;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.Toolbar;
import android.window.OnBackInvokedDispatcher;
import com.squareup.otto.Subscribe;
import org.joinmastodon.android.E;
import org.joinmastodon.android.GlobalUserPreferences;
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.fragments.BaseStatusListFragment;
import org.joinmastodon.android.fragments.ComposeFragment;
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.Snackbar;
import org.joinmastodon.android.ui.drawables.VideoPlayerSeekBarThumbDrawable;
import org.joinmastodon.android.ui.utils.BlurHashDecoder;
import org.joinmastodon.android.ui.utils.UiUtils;
import org.parceler.Parcels;
import java.io.File;
import java.io.FileOutputStream;
@ -77,14 +90,17 @@ import java.util.stream.Collectors;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.palette.graphics.ColorUtils;
import androidx.recyclerview.widget.RecyclerView;
import androidx.viewpager2.widget.ViewPager2;
import me.grishka.appkit.Nav;
import me.grishka.appkit.imageloader.ImageCache;
import me.grishka.appkit.imageloader.ViewImageLoader;
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
import me.grishka.appkit.utils.BindableViewHolder;
import me.grishka.appkit.utils.CubicBezierInterpolator;
import me.grishka.appkit.utils.V;
import me.grishka.appkit.views.BottomSheet;
import me.grishka.appkit.views.FragmentRootLinearLayout;
import okio.BufferedSink;
import okio.Okio;
@ -97,11 +113,13 @@ public class PhotoViewer implements ZoomPanView.Listener{
private Activity activity;
private List<Attachment> attachments;
private int[] backgroundColors;
private int currentIndex;
private WindowManager wm;
private Listener listener;
private Status status;
private String accountID;
private BaseStatusListFragment<?> parentFragment;
private FrameLayout windowView;
private FragmentRootLinearLayout uiOverlay;
@ -109,19 +127,24 @@ public class PhotoViewer implements ZoomPanView.Listener{
private ColorDrawable background=new ColorDrawable(0xff000000);
private ArrayList<MediaPlayer> players=new ArrayList<>();
private int screenOnRefCount=0;
private Toolbar toolbar;
private View toolbarWrap;
private SeekBar videoSeekBar;
private TextView videoTimeView;
private ImageButton videoPlayPauseButton;
private View videoControls;
private TextView altText;
private ImageButton backButton, downloadButton;
private View bottomBar;
private View postActions;
private View replyBtn, boostBtn, favoriteBtn, shareBtn, bookmarkBtn;
private TextView replyText, boostText, favoriteText;
private boolean uiVisible=true;
private AudioManager.OnAudioFocusChangeListener audioFocusListener=this::onAudioFocusChanged;
private Runnable uiAutoHider=()->{
if(uiVisible)
toggleUI();
};
private Animator currentSheetRelatedToolbarAnimation;
private Animator currentUiVisibilityAnimation;
private boolean videoPositionNeedsUpdating;
private Runnable videoPositionUpdater=this::updateVideoPosition;
@ -157,13 +180,28 @@ public class PhotoViewer implements ZoomPanView.Listener{
}
};
public PhotoViewer(Activity activity, List<Attachment> attachments, int index, Status status, String accountID, Listener listener){
public PhotoViewer(Activity activity, BaseStatusListFragment<?> parentFragment, 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;
this.parentFragment=parentFragment;
backgroundColors=new int[this.attachments.size()];
int i=0;
float[] hsl=new float[3];
for(Attachment att:this.attachments){
if(TextUtils.isEmpty(att.blurhash)){
backgroundColors[i]=0xff000000;
}else{
ColorUtils.colorToHSL(BlurHashDecoder.decodeToSingleColor(att.blurhash) | 0xff000000, hsl);
hsl[2]=Math.min(hsl[2], 0.15f);
backgroundColors[i]=ColorUtils.HSLToColor(hsl);
}
i++;
}
wm=activity.getWindowManager();
@ -181,6 +219,9 @@ public class PhotoViewer implements ZoomPanView.Listener{
@Override
public WindowInsets dispatchApplyWindowInsets(WindowInsets insets){
int bottomInset=insets.getSystemWindowInsetBottom();
bottomBar.setPadding(bottomBar.getPaddingLeft(), bottomBar.getPaddingTop(), bottomBar.getPaddingRight(), bottomInset>0 ? Math.max(bottomInset+V.dp(8), V.dp(40)) : V.dp(12));
insets=insets.replaceSystemWindowInsets(insets.getSystemWindowInsetLeft(), insets.getSystemWindowInsetTop(), insets.getSystemWindowInsetRight(), 0);
if(Build.VERSION.SDK_INT>=29){
DisplayCutout cutout=insets.getDisplayCutout();
Insets tappable=insets.getTappableElementInsets();
@ -189,18 +230,14 @@ public class PhotoViewer implements ZoomPanView.Listener{
int leftInset=Math.max(0, cutout.getSafeInsetLeft()-tappable.left);
int rightInset=Math.max(0, cutout.getSafeInsetRight()-tappable.right);
toolbarWrap.setPadding(leftInset, 0, rightInset, 0);
videoControls.setPadding(leftInset, 0, rightInset, 0);
bottomBar.setPadding(leftInset, bottomBar.getPaddingTop(), rightInset, bottomBar.getPaddingBottom());
}else{
toolbarWrap.setPadding(0, 0, 0, 0);
videoControls.setPadding(0, 0, 0, 0);
bottomBar.setPadding(0, bottomBar.getPaddingTop(), 0, bottomBar.getPaddingBottom());
}
insets=insets.replaceSystemWindowInsets(tappable.left, tappable.top, tappable.right, tappable.bottom);
insets=insets.replaceSystemWindowInsets(tappable.left, tappable.top, tappable.right, bottomBar.getVisibility()==View.VISIBLE ? 0 : tappable.bottom);
}
uiOverlay.dispatchApplyWindowInsets(insets);
int bottomInset=insets.getSystemWindowInsetBottom();
if(bottomInset>0 && bottomInset<V.dp(36)){
uiOverlay.setPadding(uiOverlay.getPaddingLeft(), uiOverlay.getPaddingTop(), uiOverlay.getPaddingRight(), V.dp(36));
}
return insets.consumeSystemWindowInsets();
}
};
@ -214,6 +251,11 @@ public class PhotoViewer implements ZoomPanView.Listener{
public void onPageSelected(int position){
onPageChanged(position);
}
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels){
updateBackgroundColor(position, positionOffset);
}
});
windowView.addView(pager);
pager.setMotionEventSplittingEnabled(false);
@ -222,19 +264,22 @@ public class PhotoViewer implements ZoomPanView.Listener{
uiOverlay.setStatusBarColor(0x80000000);
uiOverlay.setNavigationBarColor(0x80000000);
toolbarWrap=uiOverlay.findViewById(R.id.toolbar_wrap);
toolbar=uiOverlay.findViewById(R.id.toolbar);
toolbar.setNavigationOnClickListener(v->onStartSwipeToDismissTransition(0));
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->{
if(status!=null)
showInfoSheet();
else
saveCurrentFile();
return true;
});
backButton=uiOverlay.findViewById(R.id.btn_back);
backButton.setOnClickListener(v->onStartSwipeToDismissTransition(0));
downloadButton=uiOverlay.findViewById(R.id.btn_download);
downloadButton.setOnClickListener(v->saveCurrentFile());
bottomBar=uiOverlay.findViewById(R.id.bottom_bar);
postActions=uiOverlay.findViewById(R.id.post_actions);
replyBtn=uiOverlay.findViewById(R.id.reply_btn);
boostBtn=uiOverlay.findViewById(R.id.boost_btn);
favoriteBtn=uiOverlay.findViewById(R.id.favorite_btn);
bookmarkBtn=uiOverlay.findViewById(R.id.bookmark_btn);
shareBtn=uiOverlay.findViewById(R.id.share_btn);
replyText=uiOverlay.findViewById(R.id.reply);
boostText=uiOverlay.findViewById(R.id.boost);
favoriteText=uiOverlay.findViewById(R.id.favorite);
uiOverlay.setAlpha(0f);
videoControls=uiOverlay.findViewById(R.id.video_player_controls);
videoSeekBar=uiOverlay.findViewById(R.id.seekbar);
@ -247,6 +292,25 @@ public class PhotoViewer implements ZoomPanView.Listener{
videoLastTimeUpdatePosition=-1;
updateVideoTimeText(0);
}
altText=uiOverlay.findViewById(R.id.alt_text);
altText.setOnClickListener(v->showAltTextSheet());
updateAltText();
updateBackgroundColor(currentIndex, 0);
if(status==null){
bottomBar.setVisibility(View.GONE);
}else{
Paint paint=new Paint();
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.ADD));
postActions.setLayerType(View.LAYER_TYPE_HARDWARE, paint);
updatePostActions();
replyBtn.setOnClickListener(this::onPostActionClick);
boostBtn.setOnClickListener(this::onPostActionClick);
favoriteBtn.setOnClickListener(this::onPostActionClick);
bookmarkBtn.setOnClickListener(this::onPostActionClick);
shareBtn.setOnClickListener(this::onPostActionClick);
}
WindowManager.LayoutParams wlp=new WindowManager.LayoutParams(WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.MATCH_PARENT);
wlp.type=WindowManager.LayoutParams.TYPE_APPLICATION;
@ -296,6 +360,11 @@ public class PhotoViewer implements ZoomPanView.Listener{
if(fromUser){
float p=progress/10000f;
updateVideoTimeText(Math.round(p*videoDuration));
// This moves the time view in sync with the seekbar thumb, but also makes sure it doesn't go off screen
// (there must be at least 16dp between the time and the edge of the screen)
float timeX=p*(seekBar.getWidth()-V.dp(32))+V.dp(16)-videoTimeView.getWidth()/2f;
videoTimeView.setTranslationX(Math.max(-(videoTimeView.getLeft()-V.dp(16)), Math.min(timeX, videoControls.getWidth()-V.dp(16)-videoTimeView.getWidth()-videoTimeView.getLeft())));
}
}
@ -305,6 +374,14 @@ public class PhotoViewer implements ZoomPanView.Listener{
if(!uiVisible) // If dragging started during hide animation
toggleUI();
windowView.removeCallbacks(uiAutoHider);
V.setVisibilityAnimated(videoTimeView, View.VISIBLE);
postActions.animate().alpha(0f).setDuration(300).setInterpolator(CubicBezierInterpolator.DEFAULT).start();
altText.animate().alpha(0f).setDuration(300).setInterpolator(CubicBezierInterpolator.DEFAULT).start();
if(altText.getVisibility()==View.VISIBLE){
videoTimeView.setTranslationY(seekBar.getHeight()+V.dp(12));
}else{
videoTimeView.setTranslationY(-videoTimeView.getHeight()-V.dp(12));
}
}
@Override
@ -312,15 +389,24 @@ public class PhotoViewer implements ZoomPanView.Listener{
MediaPlayer player=findCurrentVideoPlayer();
if(player!=null){
float progress=seekBar.getProgress()/10000f;
player.seekTo(Math.round(progress*player.getDuration()));
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.O)
player.seekTo(Math.round(progress*player.getDuration()), MediaPlayer.SEEK_CLOSEST);
else
player.seekTo(Math.round(progress*player.getDuration()));
}
hideUiDelayed();
V.setVisibilityAnimated(videoTimeView, View.INVISIBLE);
postActions.animate().alpha(1f).setDuration(300).setInterpolator(CubicBezierInterpolator.DEFAULT).start();
altText.animate().alpha(1f).setDuration(300).setInterpolator(CubicBezierInterpolator.DEFAULT).start();
}
});
videoSeekBar.setThumb(new VideoPlayerSeekBarThumbDrawable());
E.register(this);
}
public void removeMenu(){
toolbar.getMenu().clear();
downloadButton.setVisibility(View.GONE);
}
@Override
@ -371,7 +457,7 @@ public class PhotoViewer implements ZoomPanView.Listener{
.alpha(0)
.setDuration(300)
.setInterpolator(CubicBezierInterpolator.DEFAULT)
.withEndAction(()->wm.removeView(windowView))
.withEndAction(this::onDismissed)
.start();
}
}
@ -399,6 +485,7 @@ public class PhotoViewer implements ZoomPanView.Listener{
if(receiverRegistered){
activity.unregisterReceiver(downloadCompletedReceiver);
}
E.unregister(this);
}
@Override
@ -407,21 +494,45 @@ public class PhotoViewer implements ZoomPanView.Listener{
}
private void toggleUI(){
if(currentUiVisibilityAnimation!=null)
currentUiVisibilityAnimation.cancel();
if(uiVisible){
uiOverlay.animate()
.alpha(0f)
.setDuration(250)
.setInterpolator(CubicBezierInterpolator.DEFAULT)
.withEndAction(()->uiOverlay.setVisibility(View.GONE))
.start();
AnimatorSet set=new AnimatorSet();
set.playTogether(
ObjectAnimator.ofFloat(uiOverlay, View.ALPHA, 0f),
ObjectAnimator.ofFloat(toolbarWrap, View.TRANSLATION_Y, V.dp(-32)),
ObjectAnimator.ofFloat(bottomBar, View.TRANSLATION_Y, V.dp(32))
);
set.setInterpolator(CubicBezierInterpolator.DEFAULT);
set.setDuration(250);
set.addListener(new AnimatorListenerAdapter(){
@Override
public void onAnimationEnd(Animator animation){
uiOverlay.setVisibility(View.GONE);
currentUiVisibilityAnimation=null;
}
});
currentUiVisibilityAnimation=set;
set.start();
windowView.setSystemUiVisibility(windowView.getSystemUiVisibility() | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY | View.SYSTEM_UI_FLAG_FULLSCREEN);
}else{
uiOverlay.setVisibility(View.VISIBLE);
uiOverlay.animate()
.alpha(1f)
.setDuration(300)
.setInterpolator(CubicBezierInterpolator.DEFAULT)
.start();
AnimatorSet set=new AnimatorSet();
set.playTogether(
ObjectAnimator.ofFloat(uiOverlay, View.ALPHA, 1f),
ObjectAnimator.ofFloat(toolbarWrap, View.TRANSLATION_Y, 0),
ObjectAnimator.ofFloat(bottomBar, View.TRANSLATION_Y, 0)
);
set.setInterpolator(CubicBezierInterpolator.DEFAULT);
set.setDuration(300);
set.addListener(new AnimatorListenerAdapter(){
@Override
public void onAnimationEnd(Animator animation){
currentUiVisibilityAnimation=null;
}
});
currentUiVisibilityAnimation=set;
set.start();
windowView.setSystemUiVisibility(windowView.getSystemUiVisibility() & ~(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY | View.SYSTEM_UI_FLAG_FULLSCREEN));
if(attachments.get(currentIndex).type==Attachment.Type.VIDEO)
hideUiDelayed(5000);
@ -448,6 +559,105 @@ public class PhotoViewer implements ZoomPanView.Listener{
videoLastTimeUpdatePosition=-1;
updateVideoTimeText(0);
}
updateAltText();
}
private void updateAltText(){
Attachment att=attachments.get(currentIndex);
if(TextUtils.isEmpty(att.description)){
altText.setVisibility(View.GONE);
}else{
altText.setVisibility(View.VISIBLE);
altText.setText(att.description);
altText.setMaxLines(att.type==Attachment.Type.VIDEO ? 3 : 4);
}
}
private void updateBackgroundColor(int position, float positionOffset){
int color;
if(positionOffset==0){
color=backgroundColors[position];
}else{
color=UiUtils.alphaBlendColors(backgroundColors[position], backgroundColors[position+1], positionOffset);
}
int alpha=background.getAlpha();
background.setColor(color);
background.setAlpha(alpha);
uiOverlay.setStatusBarColor(color & 0xe6ffffff);
uiOverlay.setNavigationBarColor(color & 0xe6ffffff);
bottomBar.setBackgroundTintList(ColorStateList.valueOf(color));
}
private void updatePostActions(){
bindActionButton(replyText, status.repliesCount);
bindActionButton(boostText, status.reblogsCount);
bindActionButton(favoriteText, status.favouritesCount);
boostBtn.setSelected(status.reblogged);
favoriteBtn.setSelected(status.favourited);
bookmarkBtn.setSelected(status.bookmarked);
bookmarkBtn.setContentDescription(activity.getString(status.bookmarked ? R.string.remove_bookmark : R.string.add_bookmark));
boolean isOwn=status.account.id.equals(AccountSessionManager.getInstance().getAccount(accountID).self.id);
boostBtn.setEnabled(status.visibility==StatusPrivacy.PUBLIC || status.visibility==StatusPrivacy.UNLISTED
|| (status.visibility==StatusPrivacy.PRIVATE && isOwn));
boostBtn.setAlpha(boostBtn.isEnabled() ? 1 : 0.5f);
Drawable d=activity.getResources().getDrawable(switch(status.visibility){
case PUBLIC, UNLISTED -> R.drawable.ic_boost;
case PRIVATE -> isOwn ? R.drawable.ic_boost_private : R.drawable.ic_boost_disabled_24px;
case DIRECT -> R.drawable.ic_boost_disabled_24px;
}, activity.getTheme());
d.setBounds(0, 0, V.dp(20), V.dp(20));
boostText.setCompoundDrawablesRelative(d, null, null, null);
}
private void bindActionButton(TextView btn, long count){
if(count>0){
btn.setText(UiUtils.abbreviateNumber(count));
btn.setCompoundDrawablePadding(V.dp(6));
}else{
btn.setText("");
btn.setCompoundDrawablePadding(0);
}
}
private void onPostActionClick(View view){
int id=view.getId();
if(id==R.id.boost_btn){
if(status!=null){
AccountSessionManager.get(accountID).getStatusInteractionController().setReblogged(status, !status.reblogged);
}
}else if(id==R.id.favorite_btn){
if(status!=null){
AccountSessionManager.get(accountID).getStatusInteractionController().setFavorited(status, !status.favourited);
}
}else if(id==R.id.share_btn){
if(status!=null){
UiUtils.openSystemShareSheet(activity, status);
}
}else if(id==R.id.bookmark_btn){
if(status!=null){
AccountSessionManager.get(accountID).getStatusInteractionController().setBookmarked(status, !status.bookmarked);
}
}else if(id==R.id.reply_btn){
parentFragment.maybeShowPreReplySheet(status, ()->{
onDismissed();
Bundle args=new Bundle();
args.putString("account", accountID);
args.putParcelable("replyTo", Parcels.wrap(status));
Nav.go(activity, ComposeFragment.class, args);
});
}
}
@Subscribe
public void onStatusCountersUpdated(StatusCountersUpdatedEvent ev){
if(status!=null && ev.id.equals(status.id)){
status.reblogsCount=ev.reblogs;
status.favouritesCount=ev.favorites;
status.reblogged=ev.reblogged;
status.favourited=ev.favorited;
status.bookmarked=ev.bookmarked;
updatePostActions();
}
}
/**
@ -690,91 +900,12 @@ public class PhotoViewer implements ZoomPanView.Listener{
}
}
private void showInfoSheet(){
private void showAltTextSheet(){
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);
}
}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);
BottomSheet sheet=new AltTextSheet(new ContextThemeWrapper(activity, UiUtils.getThemeForUserPreference(activity, GlobalUserPreferences.ThemePreference.DARK)),
attachments.get(currentIndex));
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;
}
});
sheet.getWindow().getDecorView().setSystemUiVisibility(sheet.getWindow().getDecorView().getSystemUiVisibility() & ~View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR);
}
public interface Listener{

View File

@ -1,182 +0,0 @@
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(me.grishka.appkit.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.setContentDescription(context.getString(R.string.back));
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.setContentDescription(context.getString(R.string.info));
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

@ -58,6 +58,12 @@ public class BlurHashDecoder{
return composeBitmap(width, height, numCompX, numCompY, colors, useCache);
}
public static int decodeToSingleColor(String hash){
if(hash.length()<6)
return 0;
return decode83(hash, 2, 6) & 0xFFFFFF;
}
private static int decode83(String str, int from, int to){
int result=0;
for(int i=from;i<to;i++){

View File

@ -725,7 +725,11 @@ public class UiUtils{
}
public static void setUserPreferredTheme(Context context){
context.setTheme(switch(GlobalUserPreferences.theme){
context.setTheme(getThemeForUserPreference(context, GlobalUserPreferences.theme));
}
public static int getThemeForUserPreference(Context context, GlobalUserPreferences.ThemePreference pref){
return switch(pref){
case AUTO -> switch(getColorContrastMode(context)){
case DEFAULT -> R.style.Theme_Mastodon_AutoLightDark;
case MEDIUM -> R.style.Theme_Mastodon_AutoLightDark_MediumContrast;
@ -741,7 +745,7 @@ public class UiUtils{
case MEDIUM -> R.style.Theme_Mastodon_Dark_MediumContrast;
case HIGH -> R.style.Theme_Mastodon_Dark_HighContrast;
};
});
};
}
public static boolean isDarkTheme(){

View File

@ -0,0 +1,72 @@
package org.joinmastodon.android.ui.views;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.LinearGradient;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Shader;
import android.graphics.Typeface;
import android.text.Layout;
import android.util.AttributeSet;
import android.widget.TextView;
import org.joinmastodon.android.R;
import me.grishka.appkit.utils.CustomViewHelper;
public class PhotoViewerAltTextView extends TextView implements CustomViewHelper{
private String moreText;
private Paint morePaint=new Paint(), clearPaint=new Paint();
private Matrix matrix=new Matrix();
private LinearGradient gradient, rtlGradient;
public PhotoViewerAltTextView(Context context){
this(context, null);
}
public PhotoViewerAltTextView(Context context, AttributeSet attrs){
this(context, attrs, 0);
}
public PhotoViewerAltTextView(Context context, AttributeSet attrs, int defStyle){
super(context, attrs, defStyle);
moreText=context.getString(R.string.text_show_more).toUpperCase();
clearPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
gradient=new LinearGradient(0, 0, dp(56), 0, 0x00ffffff, 0xffffffff, Shader.TileMode.CLAMP);
rtlGradient=new LinearGradient(0, 0, dp(56), 0, 0xffffffff, 0x00ffffff, Shader.TileMode.CLAMP);
setLayerType(LAYER_TYPE_HARDWARE, null);
}
@Override
protected void onDraw(Canvas canvas){
super.onDraw(canvas);
Layout layout=getLayout();
if(layout.getLineCount()>=getMaxLines() && layout.getEllipsisCount(layout.getLineCount()-1)>0){
int lastLine=layout.getLineCount()-1;
morePaint.set(getPaint());
morePaint.setTypeface(Typeface.DEFAULT_BOLD);
float moreWidth=morePaint.measureText(moreText);
int lineTop=layout.getLineTop(lastLine);
int lineBottom=layout.getLineBottom(lastLine);
int viewRight=getWidth()-getPaddingRight();
int gradientWidth=dp(56);
if(layout.getParagraphDirection(lastLine)==Layout.DIR_RIGHT_TO_LEFT){
matrix.setTranslate(getPaddingLeft()+moreWidth, lineTop);
rtlGradient.setLocalMatrix(matrix);
clearPaint.setShader(rtlGradient);
canvas.drawRect(getPaddingLeft(), lineTop, getPaddingLeft()+moreWidth+gradientWidth, lineBottom, clearPaint);
canvas.drawText(moreText, getPaddingLeft(), layout.getLineBaseline(lastLine), morePaint);
}else{
matrix.setTranslate(viewRight-moreWidth-gradientWidth, lineTop);
gradient.setLocalMatrix(matrix);
clearPaint.setShader(gradient);
canvas.drawRect(viewRight-moreWidth-gradientWidth, lineTop, viewRight, lineBottom, clearPaint);
canvas.drawText(moreText, viewRight-moreWidth, layout.getLineBaseline(lastLine), morePaint);
}
}
}
}

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<gradient
android:type="linear"
android:startColor="#00000000"
android:endColor="#E6000000"
android:angle="270"/>
</shape>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android" android:color="@color/m3_white_overlay">
<item android:gravity="center" android:width="32dp" android:height="32dp">
<shape android:shape="oval">
<solid android:color="#80000000"/>
</shape>
</item>
</ripple>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="20dp"
android:height="20dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:fillColor="@android:color/white"
android:pathData="M240,816L240,216Q240,186.3 261.15,165.15Q282.3,144 312,144L648,144Q677.7,144 698.85,165.15Q720,186.3 720,216L720,816L480,720L240,816ZM312,709L480,642L648,709L648,216Q648,216 648,216Q648,216 648,216L312,216Q312,216 312,216Q312,216 312,216L312,709ZM312,216L312,216Q312,216 312,216Q312,216 312,216L648,216Q648,216 648,216Q648,216 648,216L648,216L480,216L312,216Z"/>
</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_bookmark_fill1_20px" android:state_selected="true"/>
<item android:drawable="@drawable/ic_bookmark_20px"/>
</selector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="20dp"
android:height="20dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:fillColor="@android:color/white"
android:pathData="M240,816L240,216Q240,186 261,165Q282,144 312,144L648,144Q678,144 699,165Q720,186 720,216L720,816L480,720L240,816Z"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="20dp"
android:height="20dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:fillColor="@android:color/white"
android:pathData="M291,720L240,669L429,480L240,291L291,240L480,429L669,240L720,291L531,480L720,669L669,720L480,531L291,720Z"/>
</vector>

View File

@ -1,27 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:gravity="center_vertical">
<item android:gravity="center_vertical" android:left="-4dp" android:right="-4dp">
<shape>
<solid android:color="#69ffffff"/>
<corners android:radius="1dp"/>
<size android:height="2dp"/>
<solid android:color="#40ffffff"/>
<corners android:radius="16dp"/>
<size android:height="8dp"/>
</shape>
</item>
<item android:gravity="center_vertical" android:id="@android:id/secondaryProgress">
<clip>
<shape>
<solid android:color="#40ffffff"/>
<corners android:radius="1dp"/>
<size android:height="2dp"/>
</shape>
</clip>
<item android:gravity="center_vertical|end" android:width="4dp" android:height="4dp" android:end="-2dp">
<shape android:shape="oval">
<solid android:color="#fff"/>
</shape>
</item>
<item android:gravity="center_vertical" android:id="@android:id/progress">
<item android:gravity="center_vertical" android:id="@android:id/progress" android:left="-4dp" android:right="-4dp">
<clip>
<shape>
<solid android:color="#fff"/>
<corners android:radius="1dp"/>
<size android:height="2dp"/>
<corners android:radius="16dp"/>
<size android:height="8dp"/>
</shape>
</clip>
</item>

View File

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<me.grishka.appkit.views.FragmentRootLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:aapt="http://schemas.android.com/aapt"
android:id="@+id/photo_viewer_overlay"
android:layout_width="match_parent"
android:layout_height="match_parent"
@ -13,66 +14,222 @@
<FrameLayout
android:id="@+id/toolbar_wrap"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="top"
android:background="#80000000">
android:layout_height="?android:actionBarSize"
android:layout_gravity="top">
<Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?android:attr/actionBarSize"
android:elevation="0dp"
android:navigationIcon="@drawable/ic_arrow_back"
android:navigationContentDescription="@string/back"
android:theme="@style/Theme.Mastodon.Toolbar.Profile"
android:background="@null"/>
<ImageButton
android:id="@+id/btn_back"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_gravity="center_vertical|start"
android:layout_marginStart="8dp"
android:src="@drawable/ic_close_20px"
android:tint="#fff"
android:background="@drawable/bg_photo_viewer_toolbar_button"
android:contentDescription="@string/back"/>
<ImageButton
android:id="@+id/btn_download"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_gravity="center_vertical|end"
android:layout_marginEnd="8dp"
android:src="@drawable/ic_download_20px"
android:tint="#fff"
android:background="@drawable/bg_photo_viewer_toolbar_button"
android:contentDescription="@string/download"/>
</FrameLayout>
<RelativeLayout
android:id="@+id/video_player_controls"
<LinearLayout
android:id="@+id/bottom_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:background="#80000000">
android:orientation="vertical"
android:paddingTop="64dp"
android:background="@drawable/bg_photo_viewer_bottom"
android:clipChildren="false"
android:clipToPadding="false">
<SeekBar
android:id="@+id/seekbar"
<RelativeLayout
android:id="@+id/video_player_controls"
android:layout_width="match_parent"
android:layout_height="34dp"
android:layout_height="wrap_content">
<ImageButton
android:id="@+id/play_pause_btn"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_alignParentStart="true"
android:layout_marginStart="8dp"
android:src="@drawable/ic_pause_24"
android:tint="#fff"
android:contentDescription="@string/pause"
android:background="?android:selectableItemBackgroundBorderless"/>
<SeekBar
android:id="@+id/seekbar"
android:layout_width="match_parent"
android:layout_height="40dp"
android:layout_toEndOf="@id/play_pause_btn"
android:layout_marginEnd="12dp"
android:max="10000"
android:splitTrack="false"
android:layerType="hardware"
android:background="@null"
android:progressDrawable="@drawable/seekbar_video_player"/>
<TextView
android:id="@+id/time"
android:layout_width="wrap_content"
android:layout_height="24dp"
android:layout_alignStart="@id/seekbar"
android:layout_alignTop="@id/seekbar"
android:gravity="center_vertical"
android:textAppearance="@style/m3_title_medium"
android:textColor="#e6ffffff"
android:fontFeatureSettings="'tnum'"
android:textStyle="bold"
android:visibility="invisible"
tools:text="1:23 / 4:56"/>
</RelativeLayout>
<org.joinmastodon.android.ui.views.PhotoViewerAltTextView
android:id="@+id/alt_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:textAppearance="@style/m3_body_medium"
android:textColor="#E6FFFFFF"
android:ellipsize="end"
tools:text="Alt text goes here"/>
<LinearLayout
android:id="@+id/post_actions"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="36dp"
android:layout_marginBottom="-8dp"
android:layout_marginTop="8dp"
android:max="10000"
android:progressDrawable="@drawable/seekbar_video_player"
android:thumb="@drawable/seekbar_video_player_thumb"/>
android:paddingLeft="8dp"
android:paddingRight="8dp">
<ImageButton
android:id="@+id/play_pause_btn"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_below="@id/seekbar"
android:layout_alignParentStart="true"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:layout_marginBottom="8dp"
android:src="@drawable/ic_pause_24"
android:tint="#fff"
android:contentDescription="@string/pause"
android:background="?android:selectableItemBackgroundBorderless"/>
<FrameLayout
android:id="@+id/reply_btn"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_marginEnd="8dp"
android:paddingHorizontal="8dp"
android:background="?android:actionBarItemBackground"
android:minWidth="64dp">
<TextView
android:id="@+id/reply"
android:layout_width="wrap_content"
android:layout_height="24dp"
android:layout_gravity="center|start"
android:drawableStart="@drawable/ic_reply_20px"
android:drawablePadding="6dp"
android:drawableTint="#80ffffff"
android:textColor="#80ffffff"
android:gravity="center_vertical"
android:textAppearance="@style/m3_label_medium"
android:duplicateParentState="true"
tools:text="123"/>
</FrameLayout>
<TextView
android:id="@+id/time"
android:layout_width="wrap_content"
android:layout_height="24dp"
android:layout_below="@id/seekbar"
android:layout_alignParentEnd="true"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:gravity="center_vertical"
android:textAppearance="@style/m3_body_large"
android:textColor="#fff"
tools:text="1:23 / 4:56"/>
<FrameLayout
android:id="@+id/boost_btn"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_marginEnd="8dp"
android:paddingHorizontal="8dp"
android:background="?android:actionBarItemBackground"
android:minWidth="64dp">
<TextView
android:id="@+id/boost"
android:layout_width="wrap_content"
android:layout_height="24dp"
android:layout_gravity="center|start"
android:drawableStart="@drawable/ic_repeat_selector"
android:drawablePadding="6dp"
android:drawableTint="#80ffffff"
android:textColor="#80ffffff"
android:gravity="center_vertical"
android:textAppearance="@style/m3_label_medium"
android:duplicateParentState="true"
tools:text="123"/>
</FrameLayout>
</RelativeLayout>
<FrameLayout
android:id="@+id/favorite_btn"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:paddingHorizontal="8dp"
android:background="?android:actionBarItemBackground"
android:minWidth="64dp">
<TextView
android:id="@+id/favorite"
android:layout_width="wrap_content"
android:layout_height="24dp"
android:layout_gravity="center|start"
android:drawableStart="@drawable/ic_star_selector"
android:drawablePadding="6dp"
android:drawableTint="#80ffffff"
android:textColor="#80ffffff"
android:gravity="center_vertical"
android:textAppearance="@style/m3_label_medium"
android:duplicateParentState="true"
tools:text="123"/>
</FrameLayout>
<Space
android:layout_width="0px"
android:layout_height="1px"
android:layout_weight="1"/>
<FrameLayout
android:id="@+id/bookmark_btn"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:paddingHorizontal="8dp"
android:background="?android:actionBarItemBackground"
android:minWidth="34dp">
<ImageView
android:id="@+id/bookmark"
android:layout_width="wrap_content"
android:layout_height="24dp"
android:layout_gravity="center"
android:src="@drawable/ic_bookmark_20px_selector"
android:tint="#80ffffff"
android:tintMode="src_in"
android:duplicateParentState="true"
android:gravity="center_vertical"/>
</FrameLayout>
<FrameLayout
android:id="@+id/share_btn"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:paddingHorizontal="8dp"
android:background="?android:actionBarItemBackground"
android:minWidth="34dp">
<ImageView
android:id="@+id/share"
android:layout_width="wrap_content"
android:layout_height="24dp"
android:layout_gravity="center"
android:src="@drawable/ic_share_20px"
android:tint="#80ffffff"
android:tintMode="src_in"
android:contentDescription="@string/share_toot_title"
android:gravity="center_vertical"/>
</FrameLayout>
</LinearLayout>
</LinearLayout>
</FrameLayout>

View File

@ -816,4 +816,5 @@
<string name="moderation_warning_action_silence">Your account has been limited.</string>
<string name="moderation_warning_action_suspend">Your account has been suspended.</string>
<string name="moderation_warning_learn_more">Learn more</string>
<string name="text_show_more">More</string>
</resources>