Media viewer redesign (AND-196)
This commit is contained in:
parent
2ad50cd972
commit
3dcc6d0013
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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)));
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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{
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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++){
|
||||
|
@ -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(){
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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>
|
@ -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>
|
9
mastodon/src/main/res/drawable/ic_bookmark_20px.xml
Normal file
9
mastodon/src/main/res/drawable/ic_bookmark_20px.xml
Normal 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>
|
@ -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>
|
@ -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>
|
9
mastodon/src/main/res/drawable/ic_close_20px.xml
Normal file
9
mastodon/src/main/res/drawable/ic_close_20px.xml
Normal 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>
|
@ -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>
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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>
|
Loading…
x
Reference in New Issue
Block a user