New sensitive media design
This commit is contained in:
@@ -74,6 +74,7 @@ public class AccountTimelineFragment extends StatusListFragment{
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
view.setBackground(null); // prevents unnecessary overdraw
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -428,35 +428,6 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
||||
}
|
||||
}
|
||||
|
||||
public void onVisibilityIconClick(HeaderStatusDisplayItem.Holder holder){
|
||||
Status status=holder.getItem().status;
|
||||
status.spoilerRevealed=!status.spoilerRevealed;
|
||||
if(!TextUtils.isEmpty(status.spoilerText)){
|
||||
TextStatusDisplayItem.Holder text=findHolderOfType(holder.getItemID(), TextStatusDisplayItem.Holder.class);
|
||||
if(text!=null){
|
||||
adapter.notifyItemChanged(text.getAbsoluteAdapterPosition());
|
||||
}
|
||||
}
|
||||
holder.rebind();
|
||||
updateImagesSpoilerState(status, holder.getItemID());
|
||||
}
|
||||
|
||||
protected void updateImagesSpoilerState(Status status, String itemID){
|
||||
ArrayList<Integer> updatedPositions=new ArrayList<>();
|
||||
MediaGridStatusDisplayItem.Holder mediaGrid=findHolderOfType(itemID, MediaGridStatusDisplayItem.Holder.class);
|
||||
if(mediaGrid!=null){
|
||||
mediaGrid.setRevealed(status.spoilerRevealed);
|
||||
updatedPositions.add(mediaGrid.getAbsoluteAdapterPosition()-getMainAdapterOffset());
|
||||
}
|
||||
int i=0;
|
||||
for(StatusDisplayItem item:displayItems){
|
||||
if(itemID.equals(item.parentID) && item instanceof MediaGridStatusDisplayItem && !updatedPositions.contains(i)){
|
||||
adapter.notifyItemChanged(i);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
public void onGapClick(GapStatusDisplayItem.Holder item){}
|
||||
|
||||
public String getAccountID(){
|
||||
@@ -622,10 +593,7 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
||||
}
|
||||
|
||||
private class StatusListItemDecoration extends RecyclerView.ItemDecoration{
|
||||
private Paint dividerPaint=new Paint(), hiddenMediaPaint=new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
private Typeface mediumTypeface=Typeface.create("sans-serif-medium", Typeface.NORMAL);
|
||||
private Layout mediaHiddenTitleLayout, mediaHiddenTextLayout, tapToRevealTextLayout;
|
||||
private int currentMediaHiddenLayoutsWidth=0;
|
||||
private Paint dividerPaint=new Paint();
|
||||
|
||||
{
|
||||
dividerPaint.setColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Outline));
|
||||
@@ -646,73 +614,5 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDrawOver(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state){
|
||||
for(int i=0;i<parent.getChildCount();i++){
|
||||
View child=parent.getChildAt(i);
|
||||
RecyclerView.ViewHolder holder=parent.getChildViewHolder(child);
|
||||
if(holder instanceof MediaGridStatusDisplayItem.Holder imgHolder){
|
||||
if(!imgHolder.getItem().status.spoilerRevealed && TextUtils.isEmpty(imgHolder.getItem().status.spoilerText)){
|
||||
hiddenMediaPaint.setColor(0x80000000);
|
||||
c.drawRect(child.getX(), child.getY(), child.getX()+child.getWidth(), child.getY()+child.getHeight(), hiddenMediaPaint);
|
||||
}
|
||||
}
|
||||
}
|
||||
for(int i=0;i<parent.getChildCount();i++){
|
||||
View child=parent.getChildAt(i);
|
||||
RecyclerView.ViewHolder holder=parent.getChildViewHolder(child);
|
||||
if(holder instanceof MediaGridStatusDisplayItem.Holder imgHolder){
|
||||
if(!imgHolder.getItem().status.spoilerRevealed){
|
||||
if(TextUtils.isEmpty(imgHolder.getItem().status.spoilerText)){
|
||||
int listWidth=getListWidthForMediaLayout();
|
||||
int width=Math.min(listWidth, V.dp(MediaGridLayout.MAX_WIDTH));
|
||||
if(currentMediaHiddenLayoutsWidth!=width)
|
||||
rebuildMediaHiddenLayouts(width-V.dp(32));
|
||||
c.save();
|
||||
float totalHeight;
|
||||
boolean hiddenByAuthor=imgHolder.getItem().status.sensitive;
|
||||
if(hiddenByAuthor)
|
||||
totalHeight=mediaHiddenTitleLayout.getHeight()+mediaHiddenTextLayout.getHeight()+V.dp(8);
|
||||
else
|
||||
totalHeight=tapToRevealTextLayout.getHeight();
|
||||
c.translate(child.getX()+V.dp(16), child.getY()+child.getHeight()/2f-totalHeight/2f);
|
||||
if(hiddenByAuthor){
|
||||
mediaHiddenTitleLayout.draw(c);
|
||||
c.translate(0, mediaHiddenTitleLayout.getHeight()+V.dp(8));
|
||||
mediaHiddenTextLayout.draw(c);
|
||||
}else{
|
||||
tapToRevealTextLayout.draw(c);
|
||||
}
|
||||
c.restore();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void rebuildMediaHiddenLayouts(int width){
|
||||
currentMediaHiddenLayoutsWidth=width;
|
||||
String title=getString(R.string.sensitive_content);
|
||||
TextPaint titlePaint=new TextPaint(Paint.ANTI_ALIAS_FLAG);
|
||||
titlePaint.setColor(getResources().getColor(R.color.gray_50));
|
||||
titlePaint.setTextSize(V.dp(22));
|
||||
titlePaint.setTypeface(mediumTypeface);
|
||||
mediaHiddenTitleLayout=StaticLayout.Builder.obtain(title, 0, title.length(), titlePaint, width)
|
||||
.setAlignment(Layout.Alignment.ALIGN_CENTER)
|
||||
.build();
|
||||
String tapToReveal=getString(R.string.tap_to_reveal);
|
||||
tapToRevealTextLayout=StaticLayout.Builder.obtain(tapToReveal, 0, tapToReveal.length(), titlePaint, width)
|
||||
.setAlignment(Layout.Alignment.ALIGN_CENTER)
|
||||
.build();
|
||||
TextPaint textPaint=new TextPaint(Paint.ANTI_ALIAS_FLAG);
|
||||
textPaint.setColor(getResources().getColor(R.color.gray_200));
|
||||
textPaint.setTextSize(V.dp(16));
|
||||
String text=getString(R.string.sensitive_content_explain);
|
||||
mediaHiddenTextLayout=StaticLayout.Builder.obtain(text, 0, text.length(), textPaint, width)
|
||||
.setAlignment(Layout.Alignment.ALIGN_CENTER)
|
||||
.setLineSpacing(V.dp(5), 1f)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,8 +5,11 @@ import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.AnimatorSet;
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.app.Activity;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.LayerDrawable;
|
||||
import android.text.TextUtils;
|
||||
import android.view.Gravity;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewTreeObserver;
|
||||
@@ -19,9 +22,11 @@ import org.joinmastodon.android.fragments.BaseStatusListFragment;
|
||||
import org.joinmastodon.android.model.Attachment;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.ui.PhotoLayoutHelper;
|
||||
import org.joinmastodon.android.ui.drawables.SpoilerStripesDrawable;
|
||||
import org.joinmastodon.android.ui.photoviewer.PhotoViewerHost;
|
||||
import org.joinmastodon.android.ui.utils.MediaAttachmentViewController;
|
||||
import org.joinmastodon.android.ui.views.FrameLayoutThatOnlyMeasuresFirstChild;
|
||||
import org.joinmastodon.android.ui.views.MaxWidthFrameLayout;
|
||||
import org.joinmastodon.android.ui.views.MediaGridLayout;
|
||||
import org.joinmastodon.android.utils.TypedObjectPool;
|
||||
|
||||
@@ -42,6 +47,7 @@ public class MediaGridStatusDisplayItem extends StatusDisplayItem{
|
||||
private final List<Attachment> attachments;
|
||||
private final ArrayList<ImageLoaderRequest> requests=new ArrayList<>();
|
||||
public final Status status;
|
||||
public boolean sensitiveRevealed;
|
||||
|
||||
public MediaGridStatusDisplayItem(String parentID, BaseStatusListFragment<?> parentFragment, PhotoLayoutHelper.TiledLayoutResult tiledLayout, List<Attachment> attachments, Status status){
|
||||
super(parentID, parentFragment);
|
||||
@@ -49,6 +55,7 @@ public class MediaGridStatusDisplayItem extends StatusDisplayItem{
|
||||
this.viewPool=parentFragment.getAttachmentViewsPool();
|
||||
this.attachments=attachments;
|
||||
this.status=status;
|
||||
sensitiveRevealed=!status.sensitive;
|
||||
for(Attachment att:attachments){
|
||||
requests.add(new UrlImageLoaderRequest(switch(att.type){
|
||||
case IMAGE -> att.url;
|
||||
@@ -85,12 +92,18 @@ public class MediaGridStatusDisplayItem extends StatusDisplayItem{
|
||||
private final View.OnClickListener clickListener=this::onViewClick, altTextClickListener=this::onAltTextClick;
|
||||
private final ArrayList<MediaAttachmentViewController> controllers=new ArrayList<>();
|
||||
|
||||
private final MaxWidthFrameLayout overlays;
|
||||
private final FrameLayout altTextWrapper;
|
||||
private final TextView altTextButton;
|
||||
private final View altTextScroller;
|
||||
private final ImageButton altTextClose;
|
||||
private final TextView altText;
|
||||
|
||||
private final View sensitiveOverlay;
|
||||
private final LayerDrawable sensitiveOverlayBG;
|
||||
private static final ColorDrawable drawableForWhenThereIsNoBlurhash=new ColorDrawable(0xffffffff);
|
||||
private final TextView hideSensitiveButton;
|
||||
|
||||
private int altTextIndex=-1;
|
||||
private Animator altTextAnimator;
|
||||
|
||||
@@ -100,15 +113,34 @@ public class MediaGridStatusDisplayItem extends StatusDisplayItem{
|
||||
layout=new MediaGridLayout(activity);
|
||||
wrapper.addView(layout);
|
||||
wrapper.setPadding(0, 0, 0, V.dp(8));
|
||||
wrapper.setClipToPadding(false);
|
||||
|
||||
activity.getLayoutInflater().inflate(R.layout.overlay_image_alt_text, wrapper);
|
||||
overlays=new MaxWidthFrameLayout(activity);
|
||||
overlays.setMaxWidth(V.dp(MediaGridLayout.MAX_WIDTH));
|
||||
wrapper.addView(overlays, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, Gravity.CENTER_HORIZONTAL));
|
||||
|
||||
activity.getLayoutInflater().inflate(R.layout.overlay_image_alt_text, overlays);
|
||||
altTextWrapper=findViewById(R.id.alt_text_wrapper);
|
||||
altTextButton=findViewById(R.id.alt_button);
|
||||
altTextScroller=findViewById(R.id.alt_text_scroller);
|
||||
altTextClose=findViewById(R.id.alt_text_close);
|
||||
altText=findViewById(R.id.alt_text);
|
||||
altTextClose.setOnClickListener(this::onAltTextCloseClick);
|
||||
wrapper.setClipToPadding(false);
|
||||
|
||||
hideSensitiveButton=(TextView) activity.getLayoutInflater().inflate(R.layout.alt_text_badge, overlays, false);
|
||||
hideSensitiveButton.setText(R.string.hide);
|
||||
FrameLayout.LayoutParams lp;
|
||||
overlays.addView(hideSensitiveButton, lp=new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, V.dp(24), Gravity.END | Gravity.BOTTOM));
|
||||
int margin=V.dp(8);
|
||||
lp.setMargins(margin, margin, margin, margin);
|
||||
|
||||
activity.getLayoutInflater().inflate(R.layout.overlay_image_sensitive, overlays);
|
||||
sensitiveOverlay=findViewById(R.id.sensitive_overlay);
|
||||
sensitiveOverlayBG=(LayerDrawable) sensitiveOverlay.getBackground();
|
||||
sensitiveOverlayBG.setDrawableByLayerId(R.id.left_drawable, new SpoilerStripesDrawable(false));
|
||||
sensitiveOverlayBG.setDrawableByLayerId(R.id.right_drawable, new SpoilerStripesDrawable(true));
|
||||
sensitiveOverlay.setOnClickListener(v->revealSensitive());
|
||||
hideSensitiveButton.setOnClickListener(v->hideSensitive());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -148,6 +180,16 @@ public class MediaGridStatusDisplayItem extends StatusDisplayItem{
|
||||
}
|
||||
altTextWrapper.setVisibility(View.GONE);
|
||||
altTextIndex=-1;
|
||||
|
||||
if(!item.sensitiveRevealed){
|
||||
sensitiveOverlay.setVisibility(View.VISIBLE);
|
||||
layout.setVisibility(View.INVISIBLE);
|
||||
updateBlurhashInSensitiveOverlay();
|
||||
}else{
|
||||
sensitiveOverlay.setVisibility(View.GONE);
|
||||
layout.setVisibility(View.VISIBLE);
|
||||
}
|
||||
hideSensitiveButton.setVisibility(item.status.sensitive ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -182,7 +224,7 @@ public class MediaGridStatusDisplayItem extends StatusDisplayItem{
|
||||
int[] loc={0, 0};
|
||||
v.getLocationInWindow(loc);
|
||||
int btnL=loc[0], btnT=loc[1];
|
||||
wrapper.getLocationInWindow(loc);
|
||||
overlays.getLocationInWindow(loc);
|
||||
btnL-=loc[0];
|
||||
btnT-=loc[1];
|
||||
|
||||
@@ -240,7 +282,7 @@ public class MediaGridStatusDisplayItem extends StatusDisplayItem{
|
||||
int[] loc={0, 0};
|
||||
btn.getLocationInWindow(loc);
|
||||
int btnL=loc[0], btnT=loc[1];
|
||||
wrapper.getLocationInWindow(loc);
|
||||
overlays.getLocationInWindow(loc);
|
||||
btnL-=loc[0];
|
||||
btnT-=loc[1];
|
||||
|
||||
@@ -276,12 +318,6 @@ public class MediaGridStatusDisplayItem extends StatusDisplayItem{
|
||||
set.start();
|
||||
}
|
||||
|
||||
public void setRevealed(boolean revealed){
|
||||
for(MediaAttachmentViewController c:controllers){
|
||||
c.setRevealed(revealed);
|
||||
}
|
||||
}
|
||||
|
||||
public MediaAttachmentViewController getViewController(int index){
|
||||
return controllers.get(index);
|
||||
}
|
||||
@@ -290,5 +326,25 @@ public class MediaGridStatusDisplayItem extends StatusDisplayItem{
|
||||
layout.setClipChildren(clip);
|
||||
wrapper.setClipChildren(clip);
|
||||
}
|
||||
|
||||
private void updateBlurhashInSensitiveOverlay(){
|
||||
Drawable d=item.attachments.get(0).blurhashPlaceholder;
|
||||
sensitiveOverlayBG.setDrawableByLayerId(R.id.blurhash, d==null ? drawableForWhenThereIsNoBlurhash : d.mutate());
|
||||
}
|
||||
|
||||
private void revealSensitive(){
|
||||
if(item.sensitiveRevealed)
|
||||
return;
|
||||
item.sensitiveRevealed=true;
|
||||
V.setVisibilityAnimated(sensitiveOverlay, View.GONE);
|
||||
layout.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
private void hideSensitive(){
|
||||
if(!item.sensitiveRevealed)
|
||||
return;
|
||||
item.sensitiveRevealed=false;
|
||||
V.setVisibilityAnimated(sensitiveOverlay, View.VISIBLE, ()->layout.setVisibility(View.INVISIBLE));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import org.joinmastodon.android.MastodonApp;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import me.grishka.appkit.utils.V;
|
||||
|
||||
public class SpoilerStripesDrawable extends Drawable{
|
||||
private Paint paint=new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
@@ -33,6 +34,8 @@ public class SpoilerStripesDrawable extends Drawable{
|
||||
canvas.translate(bounds.left, bounds.top);
|
||||
canvas.clipRect(0, 0, bounds.width(), bounds.height());
|
||||
float scale=MastodonApp.context.getResources().getDisplayMetrics().density;
|
||||
if(bounds.width()>V.dp(10))
|
||||
scale*=2;
|
||||
canvas.scale(scale, scale, 0, 0);
|
||||
|
||||
float height=bounds.height()/scale;
|
||||
|
||||
@@ -50,4 +50,10 @@ public class BlurHashDrawable extends Drawable{
|
||||
public int getIntrinsicHeight(){
|
||||
return height;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Drawable mutate(){
|
||||
return new BlurHashDrawable(bitmap, width, height);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ public class MediaAttachmentViewController{
|
||||
this.status=status;
|
||||
crossfadeDrawable.setSize(attachment.getWidth(), attachment.getHeight());
|
||||
crossfadeDrawable.setBlurhashDrawable(attachment.blurhashPlaceholder);
|
||||
crossfadeDrawable.setCrossfadeAlpha(status.spoilerRevealed ? 0f : 1f);
|
||||
crossfadeDrawable.setCrossfadeAlpha(0f);
|
||||
photo.setImageDrawable(null);
|
||||
photo.setImageDrawable(crossfadeDrawable);
|
||||
photo.setContentDescription(TextUtils.isEmpty(attachment.description) ? context.getString(R.string.media_no_description) : attachment.description);
|
||||
@@ -67,7 +67,7 @@ public class MediaAttachmentViewController{
|
||||
|
||||
public void setImage(Drawable drawable){
|
||||
crossfadeDrawable.setImageDrawable(drawable);
|
||||
if(didClear && status.spoilerRevealed)
|
||||
if(didClear)
|
||||
crossfadeDrawable.animateAlpha(0f);
|
||||
}
|
||||
|
||||
@@ -76,8 +76,4 @@ public class MediaAttachmentViewController{
|
||||
crossfadeDrawable.setImageDrawable(null);
|
||||
didClear=true;
|
||||
}
|
||||
|
||||
public void setRevealed(boolean revealed){
|
||||
crossfadeDrawable.animateAlpha(revealed ? 0f : 1f);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user