From 3dcc6d0013faac2a592c7aa3f2d9257457e7af54 Mon Sep 17 00:00:00 2001 From: Grishka Date: Mon, 28 Oct 2024 11:26:40 +0300 Subject: [PATCH] Media viewer redesign (AND-196) --- .../fragments/BaseStatusListFragment.java | 2 +- .../ComposeImageDescriptionFragment.java | 2 +- .../android/fragments/ProfileFragment.java | 4 +- .../VideoPlayerSeekBarThumbDrawable.java | 68 ++++ .../android/ui/photoviewer/PhotoViewer.java | 375 ++++++++++++------ .../ui/photoviewer/PhotoViewerInfoSheet.java | 182 --------- .../android/ui/utils/BlurHashDecoder.java | 6 + .../android/ui/utils/UiUtils.java | 8 +- .../ui/views/PhotoViewerAltTextView.java | 72 ++++ .../res/drawable/bg_photo_viewer_bottom.xml | 8 + .../bg_photo_viewer_toolbar_button.xml | 8 + .../main/res/drawable/ic_bookmark_20px.xml | 9 + .../drawable/ic_bookmark_20px_selector.xml | 5 + .../res/drawable/ic_bookmark_fill1_20px.xml | 9 + .../src/main/res/drawable/ic_close_20px.xml | 9 + .../res/drawable/seekbar_video_player.xml | 26 +- .../src/main/res/layout/photo_viewer_ui.xml | 251 +++++++++--- mastodon/src/main/res/values/strings.xml | 1 + 18 files changed, 673 insertions(+), 372 deletions(-) create mode 100644 mastodon/src/main/java/org/joinmastodon/android/ui/drawables/VideoPlayerSeekBarThumbDrawable.java delete mode 100644 mastodon/src/main/java/org/joinmastodon/android/ui/photoviewer/PhotoViewerInfoSheet.java create mode 100644 mastodon/src/main/java/org/joinmastodon/android/ui/views/PhotoViewerAltTextView.java create mode 100644 mastodon/src/main/res/drawable/bg_photo_viewer_bottom.xml create mode 100644 mastodon/src/main/res/drawable/bg_photo_viewer_toolbar_button.xml create mode 100644 mastodon/src/main/res/drawable/ic_bookmark_20px.xml create mode 100644 mastodon/src/main/res/drawable/ic_bookmark_20px_selector.xml create mode 100644 mastodon/src/main/res/drawable/ic_bookmark_fill1_20px.xml create mode 100644 mastodon/src/main/res/drawable/ic_close_20px.xml diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/BaseStatusListFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/BaseStatusListFragment.java index c67e7985f..7f4fe8ff6 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/BaseStatusListFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/BaseStatusListFragment.java @@ -186,7 +186,7 @@ public abstract class BaseStatusListFragment 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 diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/ComposeImageDescriptionFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/ComposeImageDescriptionFragment.java index efb6aa31b..719418e25 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/ComposeImageDescriptionFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/ComposeImageDescriptionFragment.java @@ -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); diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/ProfileFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/ProfileFragment.java index 88c1321c8..4292121ee 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/ProfileFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/ProfileFragment.java @@ -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))); } } diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/drawables/VideoPlayerSeekBarThumbDrawable.java b/mastodon/src/main/java/org/joinmastodon/android/ui/drawables/VideoPlayerSeekBarThumbDrawable.java new file mode 100644 index 000000000..aedd5005e --- /dev/null +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/drawables/VideoPlayerSeekBarThumbDrawable.java @@ -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); + } +} diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/photoviewer/PhotoViewer.java b/mastodon/src/main/java/org/joinmastodon/android/ui/photoviewer/PhotoViewer.java index 7331d0572..5960994e8 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/photoviewer/PhotoViewer.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/photoviewer/PhotoViewer.java @@ -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 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 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 attachments, int index, Status status, String accountID, Listener listener){ + public PhotoViewer(Activity activity, BaseStatusListFragment parentFragment, List 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 && bottomInsetonStartSwipeToDismissTransition(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{ diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/photoviewer/PhotoViewerInfoSheet.java b/mastodon/src/main/java/org/joinmastodon/android/ui/photoviewer/PhotoViewerInfoSheet.java deleted file mode 100644 index 1e17ed329..000000000 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/photoviewer/PhotoViewerInfoSheet.java +++ /dev/null @@ -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); - } -} diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/utils/BlurHashDecoder.java b/mastodon/src/main/java/org/joinmastodon/android/ui/utils/BlurHashDecoder.java index e5f9132e7..8d651113f 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/utils/BlurHashDecoder.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/utils/BlurHashDecoder.java @@ -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 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(){ diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/views/PhotoViewerAltTextView.java b/mastodon/src/main/java/org/joinmastodon/android/ui/views/PhotoViewerAltTextView.java new file mode 100644 index 000000000..633fbd079 --- /dev/null +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/views/PhotoViewerAltTextView.java @@ -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); + } + } + } +} diff --git a/mastodon/src/main/res/drawable/bg_photo_viewer_bottom.xml b/mastodon/src/main/res/drawable/bg_photo_viewer_bottom.xml new file mode 100644 index 000000000..5036ada4e --- /dev/null +++ b/mastodon/src/main/res/drawable/bg_photo_viewer_bottom.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/mastodon/src/main/res/drawable/bg_photo_viewer_toolbar_button.xml b/mastodon/src/main/res/drawable/bg_photo_viewer_toolbar_button.xml new file mode 100644 index 000000000..351ae5160 --- /dev/null +++ b/mastodon/src/main/res/drawable/bg_photo_viewer_toolbar_button.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/mastodon/src/main/res/drawable/ic_bookmark_20px.xml b/mastodon/src/main/res/drawable/ic_bookmark_20px.xml new file mode 100644 index 000000000..27b1c400c --- /dev/null +++ b/mastodon/src/main/res/drawable/ic_bookmark_20px.xml @@ -0,0 +1,9 @@ + + + diff --git a/mastodon/src/main/res/drawable/ic_bookmark_20px_selector.xml b/mastodon/src/main/res/drawable/ic_bookmark_20px_selector.xml new file mode 100644 index 000000000..5f0f8f4d7 --- /dev/null +++ b/mastodon/src/main/res/drawable/ic_bookmark_20px_selector.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/mastodon/src/main/res/drawable/ic_bookmark_fill1_20px.xml b/mastodon/src/main/res/drawable/ic_bookmark_fill1_20px.xml new file mode 100644 index 000000000..c34af3e67 --- /dev/null +++ b/mastodon/src/main/res/drawable/ic_bookmark_fill1_20px.xml @@ -0,0 +1,9 @@ + + + diff --git a/mastodon/src/main/res/drawable/ic_close_20px.xml b/mastodon/src/main/res/drawable/ic_close_20px.xml new file mode 100644 index 000000000..89d28fe2b --- /dev/null +++ b/mastodon/src/main/res/drawable/ic_close_20px.xml @@ -0,0 +1,9 @@ + + + diff --git a/mastodon/src/main/res/drawable/seekbar_video_player.xml b/mastodon/src/main/res/drawable/seekbar_video_player.xml index 4a28b332a..45ffd2460 100644 --- a/mastodon/src/main/res/drawable/seekbar_video_player.xml +++ b/mastodon/src/main/res/drawable/seekbar_video_player.xml @@ -1,27 +1,23 @@ - + - - - + + + - - - - - - - - + + + + - + - - + + diff --git a/mastodon/src/main/res/layout/photo_viewer_ui.xml b/mastodon/src/main/res/layout/photo_viewer_ui.xml index bdb38f1b0..245790cce 100644 --- a/mastodon/src/main/res/layout/photo_viewer_ui.xml +++ b/mastodon/src/main/res/layout/photo_viewer_ui.xml @@ -1,6 +1,7 @@ + android:layout_height="?android:actionBarSize" + android:layout_gravity="top"> - + + + - + android:orientation="vertical" + android:paddingTop="64dp" + android:background="@drawable/bg_photo_viewer_bottom" + android:clipChildren="false" + android:clipToPadding="false"> - + + + + + + + + + + + + + android:paddingLeft="8dp" + android:paddingRight="8dp"> - + + + - + + + - + + + + + + + + + + + + + + + + + diff --git a/mastodon/src/main/res/values/strings.xml b/mastodon/src/main/res/values/strings.xml index a9bafe0bc..8c229eb47 100644 --- a/mastodon/src/main/res/values/strings.xml +++ b/mastodon/src/main/res/values/strings.xml @@ -816,4 +816,5 @@ Your account has been limited. Your account has been suspended. Learn more + More \ No newline at end of file