Post redesign part 1

This commit is contained in:
Grishka 2024-09-17 22:06:31 +03:00
parent 192c634755
commit 3820eee174
35 changed files with 217 additions and 262 deletions

View file

@ -9,14 +9,12 @@ import org.joinmastodon.android.model.Notification;
import org.joinmastodon.android.model.Status; import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.ui.displayitems.NotificationHeaderStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.NotificationHeaderStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem; import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
import org.joinmastodon.android.ui.utils.InsetStatusItemDecoration;
import org.parceler.Parcels; import org.parceler.Parcels;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import androidx.recyclerview.widget.RecyclerView;
import me.grishka.appkit.Nav; import me.grishka.appkit.Nav;
public abstract class BaseNotificationsListFragment extends BaseStatusListFragment<Notification>{ public abstract class BaseNotificationsListFragment extends BaseStatusListFragment<Notification>{
@ -36,7 +34,8 @@ public abstract class BaseNotificationsListFragment extends BaseStatusListFragme
} }
} }
if(n.status!=null){ if(n.status!=null){
int flags=titleItem==null ? 0 : (StatusDisplayItem.FLAG_NO_FOOTER | StatusDisplayItem.FLAG_INSET | StatusDisplayItem.FLAG_NO_HEADER); // TODO
int flags=titleItem==null ? 0 : (StatusDisplayItem.FLAG_NO_FOOTER | StatusDisplayItem.FLAG_NO_HEADER);
ArrayList<StatusDisplayItem> items=StatusDisplayItem.buildItems(this, n.status, accountID, n, knownAccounts, flags); ArrayList<StatusDisplayItem> items=StatusDisplayItem.buildItems(this, n.status, accountID, n, knownAccounts, flags);
if(titleItem!=null) if(titleItem!=null)
items.add(0, titleItem); items.add(0, titleItem);
@ -111,10 +110,4 @@ public abstract class BaseNotificationsListFragment extends BaseStatusListFragme
endMark.setVisibility(View.GONE); endMark.setVisibility(View.GONE);
return v; return v;
} }
@Override
public void onViewCreated(View view, Bundle savedInstanceState){
super.onViewCreated(view, savedInstanceState);
list.addItemDecoration(new InsetStatusItemDecoration(this));
}
} }

View file

@ -29,6 +29,7 @@ import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.model.Translation; import org.joinmastodon.android.model.Translation;
import org.joinmastodon.android.ui.BetterItemAnimator; import org.joinmastodon.android.ui.BetterItemAnimator;
import org.joinmastodon.android.ui.M3AlertDialogBuilder; import org.joinmastodon.android.ui.M3AlertDialogBuilder;
import org.joinmastodon.android.ui.PhotoLayoutHelper;
import org.joinmastodon.android.ui.sheets.NonMutualPreReplySheet; import org.joinmastodon.android.ui.sheets.NonMutualPreReplySheet;
import org.joinmastodon.android.ui.sheets.OldPostPreReplySheet; import org.joinmastodon.android.ui.sheets.OldPostPreReplySheet;
import org.joinmastodon.android.ui.displayitems.AccountStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.AccountStatusDisplayItem;
@ -44,6 +45,7 @@ import org.joinmastodon.android.ui.photoviewer.PhotoViewer;
import org.joinmastodon.android.ui.photoviewer.PhotoViewerHost; import org.joinmastodon.android.ui.photoviewer.PhotoViewerHost;
import org.joinmastodon.android.ui.utils.MediaAttachmentViewController; import org.joinmastodon.android.ui.utils.MediaAttachmentViewController;
import org.joinmastodon.android.ui.utils.UiUtils; import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.ui.views.MediaGridLayout;
import org.joinmastodon.android.utils.TypedObjectPool; import org.joinmastodon.android.utils.TypedObjectPool;
import java.time.Instant; import java.time.Instant;
@ -207,6 +209,15 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
list.setClipChildren(false); list.setClipChildren(false);
gridHolder.setClipChildren(false); gridHolder.setClipChildren(false);
transitioningHolder.view.setElevation(1f); transitioningHolder.view.setElevation(1f);
int cornerMask=((MediaGridLayout.LayoutParams)holder.view.getLayoutParams()).tile.getRoundCornersMask();
if((cornerMask & PhotoLayoutHelper.CORNER_TL)!=0)
outCornerRadius[0]=V.dp(8);
if((cornerMask & PhotoLayoutHelper.CORNER_TR)!=0)
outCornerRadius[1]=V.dp(8);
if((cornerMask & PhotoLayoutHelper.CORNER_BR)!=0)
outCornerRadius[2]=V.dp(8);
if((cornerMask & PhotoLayoutHelper.CORNER_BL)!=0)
outCornerRadius[3]=V.dp(8);
return true; return true;
} }
return false; return false;

View file

@ -34,7 +34,6 @@ import org.joinmastodon.android.ui.M3AlertDialogBuilder;
import org.joinmastodon.android.ui.OutlineProviders; import org.joinmastodon.android.ui.OutlineProviders;
import org.joinmastodon.android.ui.adapters.GenericListItemsAdapter; import org.joinmastodon.android.ui.adapters.GenericListItemsAdapter;
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem; import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
import org.joinmastodon.android.ui.utils.InsetStatusItemDecoration;
import org.joinmastodon.android.ui.utils.UiUtils; import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.ui.viewcontrollers.GenericListItemsViewController; import org.joinmastodon.android.ui.viewcontrollers.GenericListItemsViewController;
import org.joinmastodon.android.ui.views.NestedRecyclerScrollView; import org.joinmastodon.android.ui.views.NestedRecyclerScrollView;

View file

@ -51,7 +51,7 @@ public class ProfileFeaturedFragment extends BaseStatusListFragment<SearchResult
ArrayList<StatusDisplayItem> items=switch(s.type){ ArrayList<StatusDisplayItem> items=switch(s.type){
case ACCOUNT -> new ArrayList<>(Collections.singletonList(new AccountStatusDisplayItem(s.id, this, s.account))); case ACCOUNT -> new ArrayList<>(Collections.singletonList(new AccountStatusDisplayItem(s.id, this, s.account)));
case HASHTAG -> new ArrayList<>(Collections.singletonList(new HashtagStatusDisplayItem(s.id, this, s.hashtag))); case HASHTAG -> new ArrayList<>(Collections.singletonList(new HashtagStatusDisplayItem(s.id, this, s.hashtag)));
case STATUS -> StatusDisplayItem.buildItems(this, s.status, accountID, s, knownAccounts, false, true); case STATUS -> StatusDisplayItem.buildItems(this, s.status, accountID, s, knownAccounts, true);
}; };
if(s.firstInSection){ if(s.firstInSection){

View file

@ -2,14 +2,12 @@ package org.joinmastodon.android.fragments;
import android.app.Activity; import android.app.Activity;
import android.os.Bundle; import android.os.Bundle;
import android.view.View;
import org.joinmastodon.android.R; import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.statuses.GetStatusEditHistory; import org.joinmastodon.android.api.requests.statuses.GetStatusEditHistory;
import org.joinmastodon.android.model.Status; import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.ui.displayitems.ReblogOrReplyLineStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.ReblogOrReplyLineStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem; import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
import org.joinmastodon.android.ui.utils.InsetStatusItemDecoration;
import org.joinmastodon.android.ui.utils.UiUtils; import org.joinmastodon.android.ui.utils.UiUtils;
import java.time.ZoneId; import java.time.ZoneId;
@ -54,7 +52,7 @@ public class StatusEditHistoryFragment extends StatusListFragment{
@Override @Override
protected List<StatusDisplayItem> buildDisplayItems(Status s){ protected List<StatusDisplayItem> buildDisplayItems(Status s){
List<StatusDisplayItem> items=StatusDisplayItem.buildItems(this, s, accountID, s, knownAccounts, true, false); List<StatusDisplayItem> items=StatusDisplayItem.buildItems(this, s, accountID, s, knownAccounts, false);
int idx=data.indexOf(s); int idx=data.indexOf(s);
if(idx>=0){ if(idx>=0){
String date=UiUtils.DATE_TIME_FORMATTER.format(s.createdAt.atZone(ZoneId.systemDefault())); String date=UiUtils.DATE_TIME_FORMATTER.format(s.createdAt.atZone(ZoneId.systemDefault()));
@ -144,12 +142,6 @@ public class StatusEditHistoryFragment extends StatusListFragment{
return items; return items;
} }
@Override
public void onViewCreated(View view, Bundle savedInstanceState){
super.onViewCreated(view, savedInstanceState);
list.addItemDecoration(new InsetStatusItemDecoration(this));
}
@Override @Override
public boolean isItemEnabled(String id){ public boolean isItemEnabled(String id){
return false; return false;

View file

@ -29,7 +29,7 @@ public abstract class StatusListFragment extends BaseStatusListFragment<Status>{
protected EventListener eventListener=new EventListener(); protected EventListener eventListener=new EventListener();
protected List<StatusDisplayItem> buildDisplayItems(Status s){ protected List<StatusDisplayItem> buildDisplayItems(Status s){
return StatusDisplayItem.buildItems(this, s, accountID, s, knownAccounts, false, true); return StatusDisplayItem.buildItems(this, s, accountID, s, knownAccounts, true);
} }
@Override @Override

View file

@ -71,6 +71,7 @@ public class ThreadFragment extends StatusListFragment{
List<StatusDisplayItem> items=super.buildDisplayItems(s); List<StatusDisplayItem> items=super.buildDisplayItems(s);
if(s.id.equals(mainStatus.id)){ if(s.id.equals(mainStatus.id)){
for(StatusDisplayItem item:items){ for(StatusDisplayItem item:items){
item.fullWidth=true;
if(item instanceof TextStatusDisplayItem text) if(item instanceof TextStatusDisplayItem text)
text.textSelectable=true; text.textSelectable=true;
else if(item instanceof FooterStatusDisplayItem footer) else if(item instanceof FooterStatusDisplayItem footer)

View file

@ -30,8 +30,6 @@ import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import me.grishka.appkit.Nav; import me.grishka.appkit.Nav;
import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse;
import me.grishka.appkit.api.SimpleCallback; import me.grishka.appkit.api.SimpleCallback;
public class SearchFragment extends BaseStatusListFragment<SearchResult>{ public class SearchFragment extends BaseStatusListFragment<SearchResult>{
@ -65,7 +63,7 @@ public class SearchFragment extends BaseStatusListFragment<SearchResult>{
return switch(s.type){ return switch(s.type){
case ACCOUNT -> Collections.singletonList(new AccountStatusDisplayItem(s.id, this, s.account)); case ACCOUNT -> Collections.singletonList(new AccountStatusDisplayItem(s.id, this, s.account));
case HASHTAG -> Collections.singletonList(new HashtagStatusDisplayItem(s.id, this, s.hashtag)); case HASHTAG -> Collections.singletonList(new HashtagStatusDisplayItem(s.id, this, s.hashtag));
case STATUS -> StatusDisplayItem.buildItems(this, s.status, accountID, s, knownAccounts, false, true); case STATUS -> StatusDisplayItem.buildItems(this, s.status, accountID, s, knownAccounts, true);
}; };
} }

View file

@ -198,7 +198,7 @@ public class ReportAddPostsChoiceFragment extends StatusListFragment{
@Override @Override
protected List<StatusDisplayItem> buildDisplayItems(Status s){ protected List<StatusDisplayItem> buildDisplayItems(Status s){
return StatusDisplayItem.buildItems(this, s, accountID, s, knownAccounts, StatusDisplayItem.FLAG_INSET | StatusDisplayItem.FLAG_NO_FOOTER | StatusDisplayItem.FLAG_CHECKABLE | StatusDisplayItem.FLAG_MEDIA_FORCE_HIDDEN); return StatusDisplayItem.buildItems(this, s, accountID, s, knownAccounts, StatusDisplayItem.FLAG_NO_FOOTER | StatusDisplayItem.FLAG_CHECKABLE | StatusDisplayItem.FLAG_MEDIA_FORCE_HIDDEN);
} }
@Override @Override

View file

@ -241,7 +241,7 @@ public class ReportReasonChoiceFragment extends StatusListFragment{
@Override @Override
protected List<StatusDisplayItem> buildDisplayItems(Status s){ protected List<StatusDisplayItem> buildDisplayItems(Status s){
return StatusDisplayItem.buildItems(this, s, accountID, s, knownAccounts, StatusDisplayItem.FLAG_INSET | StatusDisplayItem.FLAG_NO_FOOTER); return StatusDisplayItem.buildItems(this, s, accountID, s, knownAccounts, StatusDisplayItem.FLAG_NO_FOOTER);
} }
@Override @Override

View file

@ -16,6 +16,11 @@ public class PhotoLayoutHelper{
public static final int MIN_HEIGHT=563; public static final int MIN_HEIGHT=563;
public static final float GAP=1.5f; public static final float GAP=1.5f;
public static final int CORNER_TL=1;
public static final int CORNER_TR=2;
public static final int CORNER_BL=4;
public static final int CORNER_BR=8;
@NonNull @NonNull
public static TiledLayoutResult processThumbs(List<Attachment> thumbs){ public static TiledLayoutResult processThumbs(List<Attachment> thumbs){
float maxRatio=MAX_WIDTH/(float)MAX_HEIGHT; float maxRatio=MAX_WIDTH/(float)MAX_HEIGHT;
@ -33,8 +38,9 @@ public class PhotoLayoutHelper{
result.width=MAX_WIDTH;//Math.round(att.getWidth()/(float)att.getHeight()*MAX_HEIGHT); result.width=MAX_WIDTH;//Math.round(att.getWidth()/(float)att.getHeight()*MAX_HEIGHT);
} }
result.tiles=new TiledLayoutResult.Tile[]{new TiledLayoutResult.Tile(1, 1, 0, 0)}; result.tiles=new TiledLayoutResult.Tile[]{new TiledLayoutResult.Tile(1, 1, 0, 0)};
result.tiles[0].columnCount=result.tiles[0].rowCount=1;
return result; return result;
}else if(thumbs.size()==0){ }else if(thumbs.isEmpty()){
throw new IllegalArgumentException("Empty thumbs array"); throw new IllegalArgumentException("Empty thumbs array");
} }
@ -309,6 +315,11 @@ public class PhotoLayoutHelper{
result.height=Math.round(totalHeight+GAP*(optHeights.length-1)); result.height=Math.round(totalHeight+GAP*(optHeights.length-1));
} }
for(TiledLayoutResult.Tile tile:result.tiles){
tile.columnCount=result.columnSizes.length;
tile.rowCount=result.rowSizes.length;
}
return result; return result;
} }
@ -340,7 +351,7 @@ public class PhotoLayoutHelper{
} }
public static class Tile{ public static class Tile{
public int colSpan, rowSpan, startCol, startRow; public int colSpan, rowSpan, startCol, startRow, columnCount, rowCount;
public int width; public int width;
public Tile(int colSpan, int rowSpan, int startCol, int startRow){ public Tile(int colSpan, int rowSpan, int startCol, int startRow){
@ -364,6 +375,27 @@ public class PhotoLayoutHelper{
", startRow="+startRow+ ", startRow="+startRow+
'}'; '}';
} }
public int getRoundCornersMask(){
if(columnCount==1 && rowCount==1){ // Single attachment. All corners are rounded
return CORNER_TL | CORNER_TR | CORNER_BL | CORNER_BR;
}else if(colSpan==columnCount && startRow==0){ // Top attachment. Top corners are rounded
return CORNER_TL | CORNER_TR;
}else if(colSpan==columnCount && startRow==rowCount-1){ // Bottom attachment. Bottom corners are rounded
return CORNER_BL | CORNER_BR;
}else if(startCol==0 && startRow==0 && rowSpan==rowCount){ // Left attachment in a vertical layout
return CORNER_TL | CORNER_BL;
}else if(startCol==0 && startRow==0){ // Top left
return CORNER_TL;
}else if(startCol==columnCount-colSpan && startRow==0){ // Top right
return CORNER_TR;
}else if(startCol==0 && startRow==rowCount-rowSpan){ // Bottom left
return CORNER_BL;
}else if(startCol==columnCount-colSpan && startRow==rowCount-rowSpan){ // Bottom right
return CORNER_BR;
}
return 0;
}
} }
} }
} }

View file

@ -98,10 +98,13 @@ public class AudioStatusDisplayItem extends StatusDisplayItem{
image.setOutlineProvider(OutlineProviders.OVAL); image.setOutlineProvider(OutlineProviders.OVAL);
image.setClipToOutline(true); image.setClipToOutline(true);
content.setBackground(bgDrawable=new AudioAttachmentBackgroundDrawable()); content.setBackground(bgDrawable=new AudioAttachmentBackgroundDrawable());
content.setOutlineProvider(OutlineProviders.roundedRect(8));
content.setClipToOutline(true);
} }
@Override @Override
public void onBind(AudioStatusDisplayItem item){ public void onBind(AudioStatusDisplayItem item){
itemView.setPaddingRelative(V.dp(item.fullWidth ? 16 : 64), itemView.getPaddingTop(), itemView.getPaddingEnd(), itemView.getPaddingBottom());
int seconds=(int)item.attachment.getDuration(); int seconds=(int)item.attachment.getDuration();
String duration=UiUtils.formatMediaDuration(seconds); String duration=UiUtils.formatMediaDuration(seconds);
AudioPlayerService service=AudioPlayerService.getInstance(); AudioPlayerService service=AudioPlayerService.getInstance();

View file

@ -53,6 +53,7 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
private final ColorStateList buttonColors; private final ColorStateList buttonColors;
private final View replyBtn, boostBtn, favoriteBtn, shareBtn; private final View replyBtn, boostBtn, favoriteBtn, shareBtn;
private final PopupMenu boostLongTapMenu, favoriteLongTapMenu; private final PopupMenu boostLongTapMenu, favoriteLongTapMenu;
private final View spacer1, spacer2;
private final View.AccessibilityDelegate buttonAccessibilityDelegate=new View.AccessibilityDelegate(){ private final View.AccessibilityDelegate buttonAccessibilityDelegate=new View.AccessibilityDelegate(){
@Override @Override
@ -69,6 +70,8 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
boost=findViewById(R.id.boost); boost=findViewById(R.id.boost);
favorite=findViewById(R.id.favorite); favorite=findViewById(R.id.favorite);
share=findViewById(R.id.share); share=findViewById(R.id.share);
spacer1=findViewById(R.id.spacer1);
spacer2=findViewById(R.id.spacer2);
float[] hsb={0, 0, 0}; float[] hsb={0, 0, 0};
Color.colorToHSV(UiUtils.getThemeColor(activity, R.attr.colorM3Primary), hsb); Color.colorToHSV(UiUtils.getThemeColor(activity, R.attr.colorM3Primary), hsb);
@ -81,8 +84,8 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
{} {}
}, new int[]{ }, new int[]{
Color.HSVToColor(hsb), Color.HSVToColor(hsb),
UiUtils.getThemeColor(activity, R.attr.colorM3OnSurfaceVariant), UiUtils.getThemeColor(activity, R.attr.colorM3Outline),
UiUtils.getThemeColor(activity, R.attr.colorM3OnSurfaceVariant) & 0x80FFFFFF UiUtils.getThemeColor(activity, R.attr.colorM3Outline) & 0x80FFFFFF
}); });
boost.setTextColor(buttonColors); boost.setTextColor(buttonColors);
@ -120,6 +123,9 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
@Override @Override
public void onBind(FooterStatusDisplayItem item){ public void onBind(FooterStatusDisplayItem item){
spacer1.setVisibility(item.fullWidth ? View.VISIBLE : View.GONE);
spacer2.setVisibility(item.fullWidth ? View.VISIBLE : View.GONE);
itemView.setPaddingRelative(V.dp(item.fullWidth ? 8 : 56), itemView.getPaddingTop(), itemView.getPaddingEnd(), itemView.getPaddingBottom());
bindButton(reply, item.status.repliesCount); bindButton(reply, item.status.repliesCount);
bindButton(boost, item.status.reblogsCount); bindButton(boost, item.status.reblogsCount);
bindButton(favorite, item.status.favouritesCount); bindButton(favorite, item.status.favouritesCount);

View file

@ -9,7 +9,6 @@ import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.os.Parcel;
import android.text.SpannableStringBuilder; import android.text.SpannableStringBuilder;
import android.text.TextUtils; import android.text.TextUtils;
import android.view.Menu; import android.view.Menu;
@ -23,7 +22,6 @@ import android.widget.Toast;
import org.joinmastodon.android.GlobalUserPreferences; import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.R; import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships;
import org.joinmastodon.android.api.requests.statuses.GetStatusSourceText; import org.joinmastodon.android.api.requests.statuses.GetStatusSourceText;
import org.joinmastodon.android.api.requests.statuses.SetStatusConversationMuted; import org.joinmastodon.android.api.requests.statuses.SetStatusConversationMuted;
import org.joinmastodon.android.api.requests.statuses.SetStatusPinned; import org.joinmastodon.android.api.requests.statuses.SetStatusPinned;
@ -46,13 +44,10 @@ import org.joinmastodon.android.ui.utils.UiUtils;
import org.parceler.Parcels; import org.parceler.Parcels;
import java.time.Instant; import java.time.Instant;
import java.util.Collections;
import java.util.List;
import java.util.Locale; import java.util.Locale;
import androidx.annotation.LayoutRes; import androidx.annotation.LayoutRes;
import me.grishka.appkit.Nav; import me.grishka.appkit.Nav;
import me.grishka.appkit.api.APIRequest;
import me.grishka.appkit.api.Callback; import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse; import me.grishka.appkit.api.ErrorResponse;
import me.grishka.appkit.imageloader.ImageLoaderViewHolder; import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
@ -272,9 +267,7 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
extraText.setVisibility(View.VISIBLE); extraText.setVisibility(View.VISIBLE);
extraText.setText(item.extraText); extraText.setText(item.extraText);
} }
more.setVisibility(item.inset ? View.GONE : View.VISIBLE);
if(clickableThing!=null){ if(clickableThing!=null){
clickableThing.setClickable(!item.inset);
clickableThing.setContentDescription(item.parentFragment.getString(R.string.avatar_description, item.user.acct)); clickableThing.setContentDescription(item.parentFragment.getString(R.string.avatar_description, item.user.acct));
} }
} }

View file

@ -10,6 +10,7 @@ import org.joinmastodon.android.model.viewmodel.CardViewModel;
import org.joinmastodon.android.ui.viewholders.LinkCardHolder; import org.joinmastodon.android.ui.viewholders.LinkCardHolder;
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest; import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
import me.grishka.appkit.utils.V;
public class LinkCardStatusDisplayItem extends StatusDisplayItem implements LinkCardHolder.LinkCardProvider{ public class LinkCardStatusDisplayItem extends StatusDisplayItem implements LinkCardHolder.LinkCardProvider{
private final Status status; private final Status status;
@ -51,5 +52,11 @@ public class LinkCardStatusDisplayItem extends StatusDisplayItem implements Link
public Holder(Activity context, ViewGroup parent, boolean isLarge, String accountID){ public Holder(Activity context, ViewGroup parent, boolean isLarge, String accountID){
super(context, parent, isLarge, accountID); super(context, parent, isLarge, accountID);
} }
@Override
public void onBind(LinkCardStatusDisplayItem item){
super.onBind(item);
itemView.setPaddingRelative(V.dp(item.fullWidth ? 16 : 64), itemView.getPaddingTop(), itemView.getPaddingEnd(), itemView.getPaddingBottom());
}
} }
} }

View file

@ -1,22 +1,15 @@
package org.joinmastodon.android.ui.displayitems; package org.joinmastodon.android.ui.displayitems;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.app.Activity; import android.app.Activity;
import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable; import android.graphics.drawable.LayerDrawable;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log;
import android.util.Pair; import android.util.Pair;
import android.view.Gravity; import android.view.Gravity;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.FrameLayout; import android.widget.FrameLayout;
import android.widget.ImageButton;
import android.widget.TextView; import android.widget.TextView;
import org.joinmastodon.android.R; import org.joinmastodon.android.R;
@ -46,7 +39,6 @@ import java.util.Optional;
import me.grishka.appkit.imageloader.ImageLoaderViewHolder; import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest; import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest; import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
import me.grishka.appkit.utils.CubicBezierInterpolator;
import me.grishka.appkit.utils.V; import me.grishka.appkit.utils.V;
public class MediaGridStatusDisplayItem extends StatusDisplayItem{ public class MediaGridStatusDisplayItem extends StatusDisplayItem{
@ -138,6 +130,8 @@ public class MediaGridStatusDisplayItem extends StatusDisplayItem{
sensitiveOverlayBG.setDrawableByLayerId(R.id.right_drawable, new SpoilerStripesDrawable(true)); sensitiveOverlayBG.setDrawableByLayerId(R.id.right_drawable, new SpoilerStripesDrawable(true));
sensitiveOverlay.setBackground(sensitiveOverlayBG); sensitiveOverlay.setBackground(sensitiveOverlayBG);
sensitiveOverlay.setOnClickListener(v->revealSensitive()); sensitiveOverlay.setOnClickListener(v->revealSensitive());
sensitiveOverlay.setOutlineProvider(OutlineProviders.roundedRect(8));
sensitiveOverlay.setClipToOutline(true);
hideSensitiveButton.setOnClickListener(v->hideSensitive()); hideSensitiveButton.setOnClickListener(v->hideSensitive());
sensitiveText=findViewById(R.id.sensitive_text); sensitiveText=findViewById(R.id.sensitive_text);
@ -146,7 +140,7 @@ public class MediaGridStatusDisplayItem extends StatusDisplayItem{
@Override @Override
public void onBind(MediaGridStatusDisplayItem item){ public void onBind(MediaGridStatusDisplayItem item){
thereAreFailedImages=false; thereAreFailedImages=false;
wrapper.setPadding(0, 0, 0, item.inset ? 0 : V.dp(8)); wrapper.setPaddingRelative(V.dp(item.fullWidth ? 16 : 64), 0, V.dp(16), V.dp(8));
layout.setTiledLayout(item.tiledLayout); layout.setTiledLayout(item.tiledLayout);
for(MediaAttachmentViewController c:controllers){ for(MediaAttachmentViewController c:controllers){

View file

@ -71,7 +71,7 @@ public class PollOptionStatusDisplayItem extends StatusDisplayItem{
public static class Holder extends StatusDisplayItem.Holder<PollOptionStatusDisplayItem> implements ImageLoaderViewHolder{ public static class Holder extends StatusDisplayItem.Holder<PollOptionStatusDisplayItem> implements ImageLoaderViewHolder{
private final TextView text, percent; private final TextView text, percent;
private final View check, button; private final View check, button;
private final Drawable progressBg, progressBgInset; private final Drawable progressBg;
public Holder(Activity activity, ViewGroup parent){ public Holder(Activity activity, ViewGroup parent){
super(activity, R.layout.display_item_poll_option, parent); super(activity, R.layout.display_item_poll_option, parent);
@ -80,7 +80,6 @@ public class PollOptionStatusDisplayItem extends StatusDisplayItem{
check=findViewById(R.id.checkbox); check=findViewById(R.id.checkbox);
button=findViewById(R.id.button); button=findViewById(R.id.button);
progressBg=activity.getResources().getDrawable(R.drawable.bg_poll_option_voted, activity.getTheme()).mutate(); progressBg=activity.getResources().getDrawable(R.drawable.bg_poll_option_voted, activity.getTheme()).mutate();
progressBgInset=activity.getResources().getDrawable(R.drawable.bg_poll_option_voted_inset, activity.getTheme()).mutate();
itemView.setOnClickListener(this::onButtonClick); itemView.setOnClickListener(this::onButtonClick);
button.setOutlineProvider(OutlineProviders.roundedRect(20)); button.setOutlineProvider(OutlineProviders.roundedRect(20));
button.setClipToOutline(true); button.setClipToOutline(true);
@ -99,7 +98,7 @@ public class PollOptionStatusDisplayItem extends StatusDisplayItem{
percent.setVisibility(item.showResults ? View.VISIBLE : View.GONE); percent.setVisibility(item.showResults ? View.VISIBLE : View.GONE);
itemView.setClickable(!item.showResults); itemView.setClickable(!item.showResults);
if(item.showResults){ if(item.showResults){
Drawable bg=item.inset ? progressBgInset : progressBg; Drawable bg=progressBg;
bg.setLevel(Math.round(10000f*item.votesFraction)); bg.setLevel(Math.round(10000f*item.votesFraction));
button.setBackground(bg); button.setBackground(bg);
itemView.setSelected(item.isMostVoted); itemView.setSelected(item.isMostVoted);
@ -107,15 +106,10 @@ public class PollOptionStatusDisplayItem extends StatusDisplayItem{
percent.setText(String.format(Locale.getDefault(), "%d%%", Math.round(item.votesFraction*100f))); percent.setText(String.format(Locale.getDefault(), "%d%%", Math.round(item.votesFraction*100f)));
}else{ }else{
itemView.setSelected(item.poll.selectedOptions!=null && item.poll.selectedOptions.contains(item.option)); itemView.setSelected(item.poll.selectedOptions!=null && item.poll.selectedOptions.contains(item.option));
button.setBackgroundResource(item.inset ? R.drawable.bg_poll_option_clickable_inset : R.drawable.bg_poll_option_clickable); button.setBackgroundResource(R.drawable.bg_poll_option_clickable);
}
if(item.inset){
text.setTextColor(itemView.getContext().getColorStateList(R.color.poll_option_text_inset));
percent.setTextColor(itemView.getContext().getColorStateList(R.color.poll_option_text_inset));
}else{
text.setTextColor(UiUtils.getThemeColor(itemView.getContext(), R.attr.colorM3Primary));
percent.setTextColor(UiUtils.getThemeColor(itemView.getContext(), R.attr.colorM3OnSecondaryContainer));
} }
text.setTextColor(UiUtils.getThemeColor(itemView.getContext(), R.attr.colorM3Primary));
percent.setTextColor(UiUtils.getThemeColor(itemView.getContext(), R.attr.colorM3OnSecondaryContainer));
} }
@Override @Override

View file

@ -20,6 +20,7 @@ import java.util.List;
import androidx.annotation.DrawableRes; import androidx.annotation.DrawableRes;
import me.grishka.appkit.imageloader.ImageLoaderViewHolder; import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest; import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
import me.grishka.appkit.utils.V;
public class ReblogOrReplyLineStatusDisplayItem extends StatusDisplayItem{ public class ReblogOrReplyLineStatusDisplayItem extends StatusDisplayItem{
private CharSequence text; private CharSequence text;
@ -62,9 +63,15 @@ public class ReblogOrReplyLineStatusDisplayItem extends StatusDisplayItem{
@Override @Override
public void onBind(ReblogOrReplyLineStatusDisplayItem item){ public void onBind(ReblogOrReplyLineStatusDisplayItem item){
text.setText(item.text); text.setText(item.text);
text.setCompoundDrawablesRelativeWithIntrinsicBounds(item.icon, 0, 0, 0); if(item.icon!=0){
if(Build.VERSION.SDK_INT<Build.VERSION_CODES.N) Drawable icon=itemView.getContext().getDrawable(item.icon);
UiUtils.fixCompoundDrawableTintOnAndroid6(text); icon.setBounds(0, 0, V.dp(16), V.dp(16));
text.setCompoundDrawablesRelative(icon, null, null, null);
if(Build.VERSION.SDK_INT<Build.VERSION_CODES.N)
UiUtils.fixCompoundDrawableTintOnAndroid6(text);
}else{
text.setCompoundDrawables(null, null, null, null);
}
} }
@Override @Override

View file

@ -21,6 +21,7 @@ import java.util.ArrayList;
import me.grishka.appkit.imageloader.ImageLoaderViewHolder; import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest; import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
import me.grishka.appkit.utils.V;
public class SpoilerStatusDisplayItem extends StatusDisplayItem{ public class SpoilerStatusDisplayItem extends StatusDisplayItem{
public final Status status; public final Status status;
@ -88,6 +89,7 @@ public class SpoilerStatusDisplayItem extends StatusDisplayItem{
@Override @Override
public void onBind(SpoilerStatusDisplayItem item){ public void onBind(SpoilerStatusDisplayItem item){
itemView.setPaddingRelative(V.dp(item.fullWidth ? 16 : 64), itemView.getPaddingTop(), itemView.getPaddingEnd(), itemView.getPaddingBottom());
if(item.status.translationState==Status.TranslationState.SHOWN){ if(item.status.translationState==Status.TranslationState.SHOWN){
if(item.translatedTitle==null){ if(item.translatedTitle==null){
item.translatedTitle=item.status.translation.spoilerText; item.translatedTitle=item.status.translation.spoilerText;

View file

@ -35,10 +35,10 @@ import me.grishka.appkit.views.UsableRecyclerView;
public abstract class StatusDisplayItem{ public abstract class StatusDisplayItem{
public final String parentID; public final String parentID;
public final BaseStatusListFragment<?> parentFragment; public final BaseStatusListFragment<?> parentFragment;
public boolean inset; public boolean fullWidth; // aka "highlighted"
public int index; public int index;
public static final int FLAG_INSET=1; public static final int FLAG_FULL_WIDTH=1;
public static final int FLAG_NO_FOOTER=1 << 1; public static final int FLAG_NO_FOOTER=1 << 1;
public static final int FLAG_CHECKABLE=1 << 2; public static final int FLAG_CHECKABLE=1 << 2;
public static final int FLAG_MEDIA_FORCE_HIDDEN=1 << 3; public static final int FLAG_MEDIA_FORCE_HIDDEN=1 << 3;
@ -82,16 +82,14 @@ public abstract class StatusDisplayItem{
}; };
} }
public static ArrayList<StatusDisplayItem> buildItems(BaseStatusListFragment fragment, Status status, String accountID, DisplayItemsParent parentObject, Map<String, Account> knownAccounts, boolean inset, boolean addFooter){ public static ArrayList<StatusDisplayItem> buildItems(BaseStatusListFragment<?> fragment, Status status, String accountID, DisplayItemsParent parentObject, Map<String, Account> knownAccounts, boolean addFooter){
int flags=0; int flags=0;
if(inset)
flags|=FLAG_INSET;
if(!addFooter) if(!addFooter)
flags|=FLAG_NO_FOOTER; flags|=FLAG_NO_FOOTER;
return buildItems(fragment, status, accountID, parentObject, knownAccounts, flags); return buildItems(fragment, status, accountID, parentObject, knownAccounts, flags);
} }
public static ArrayList<StatusDisplayItem> buildItems(BaseStatusListFragment fragment, Status status, String accountID, DisplayItemsParent parentObject, Map<String, Account> knownAccounts, int flags){ public static ArrayList<StatusDisplayItem> buildItems(BaseStatusListFragment<?> fragment, Status status, String accountID, DisplayItemsParent parentObject, Map<String, Account> knownAccounts, int flags){
String parentID=parentObject.getID(); String parentID=parentObject.getID();
ArrayList<StatusDisplayItem> items=new ArrayList<>(); ArrayList<StatusDisplayItem> items=new ArrayList<>();
Status statusForContent=status.getContentStatus(); Status statusForContent=status.getContentStatus();
@ -99,10 +97,10 @@ public abstract class StatusDisplayItem{
boolean hideCounts=!AccountSessionManager.get(accountID).getLocalPreferences().showInteractionCounts; boolean hideCounts=!AccountSessionManager.get(accountID).getLocalPreferences().showInteractionCounts;
if((flags & FLAG_NO_HEADER)==0){ if((flags & FLAG_NO_HEADER)==0){
if(status.reblog!=null){ if(status.reblog!=null){
items.add(new ReblogOrReplyLineStatusDisplayItem(parentID, fragment, fragment.getString(R.string.user_boosted, status.account.displayName), status.account.emojis, R.drawable.ic_repeat_20px)); items.add(new ReblogOrReplyLineStatusDisplayItem(parentID, fragment, fragment.getString(R.string.user_boosted, status.account.displayName), status.account.emojis, R.drawable.ic_repeat_wght700_20px));
}else if(status.inReplyToAccountId!=null && knownAccounts.containsKey(status.inReplyToAccountId)){ }else if(status.inReplyToAccountId!=null && knownAccounts.containsKey(status.inReplyToAccountId)){
Account account=Objects.requireNonNull(knownAccounts.get(status.inReplyToAccountId)); Account account=Objects.requireNonNull(knownAccounts.get(status.inReplyToAccountId));
items.add(new ReblogOrReplyLineStatusDisplayItem(parentID, fragment, fragment.getString(R.string.in_reply_to, account.displayName), account.emojis, R.drawable.ic_reply_20px)); items.add(new ReblogOrReplyLineStatusDisplayItem(parentID, fragment, fragment.getString(R.string.in_reply_to, account.displayName), account.emojis, R.drawable.ic_reply_wght700_20px));
} }
if((flags & FLAG_CHECKABLE)!=0) if((flags & FLAG_CHECKABLE)!=0)
items.add(header=new CheckableHeaderStatusDisplayItem(parentID, statusForContent.account, statusForContent.createdAt, fragment, accountID, statusForContent, null)); items.add(header=new CheckableHeaderStatusDisplayItem(parentID, statusForContent.account, statusForContent.createdAt, fragment, accountID, statusForContent, null));
@ -185,21 +183,20 @@ public abstract class StatusDisplayItem{
items.add(new GapStatusDisplayItem(parentID, fragment)); items.add(new GapStatusDisplayItem(parentID, fragment));
} }
int i=1; int i=1;
boolean inset=(flags & FLAG_INSET)!=0; boolean fullWidth=(flags & FLAG_FULL_WIDTH)!=0;
for(StatusDisplayItem item:items){ for(StatusDisplayItem item:items){
item.inset=inset; item.fullWidth=fullWidth;
item.index=i++; item.index=i++;
} }
if(items!=contentItems){ if(items!=contentItems){
for(StatusDisplayItem item:contentItems){ for(StatusDisplayItem item:contentItems){
item.inset=inset;
item.index=i++; item.index=i++;
} }
} }
return items; return items;
} }
public static void buildPollItems(String parentID, BaseStatusListFragment fragment, Poll poll, Status status, List<StatusDisplayItem> items){ public static void buildPollItems(String parentID, BaseStatusListFragment<?> fragment, Poll poll, Status status, List<StatusDisplayItem> items){
int i=0; int i=0;
for(Poll.Option opt:poll.options){ for(Poll.Option opt:poll.options){
items.add(new PollOptionStatusDisplayItem(parentID, poll, i, fragment, status)); items.add(new PollOptionStatusDisplayItem(parentID, poll, i, fragment, status));

View file

@ -3,7 +3,7 @@ package org.joinmastodon.android.ui.displayitems;
import android.app.Activity; import android.app.Activity;
import android.graphics.drawable.Animatable; import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.text.SpannableStringBuilder; import android.util.TypedValue;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.ViewStub; import android.view.ViewStub;
@ -95,7 +95,9 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
text.setInvalidateOnEveryFrame(false); text.setInvalidateOnEveryFrame(false);
itemView.setClickable(false); itemView.setClickable(false);
text.setPadding(text.getPaddingLeft(), item.reduceTopPadding ? V.dp(8) : V.dp(12), text.getPaddingRight(), text.getPaddingBottom()); text.setPadding(text.getPaddingLeft(), item.reduceTopPadding ? V.dp(8) : V.dp(12), text.getPaddingRight(), text.getPaddingBottom());
text.setTextColor(UiUtils.getThemeColor(text.getContext(), item.inset ? R.attr.colorM3OnSurfaceVariant : R.attr.colorM3OnSurface)); itemView.setPaddingRelative(V.dp(item.fullWidth ? 0 : 48), 0, 0, 0);
text.setTextColor(UiUtils.getThemeColor(text.getContext(), R.attr.colorM3OnSurface));
text.setTextSize(TypedValue.COMPLEX_UNIT_SP, item.fullWidth ? 18 : 16);
updateTranslation(false); updateTranslation(false);
} }

View file

@ -8,7 +8,6 @@ import android.graphics.Path;
import android.graphics.Rect; import android.graphics.Rect;
import android.graphics.RectF; import android.graphics.RectF;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.util.Log;
import android.view.GestureDetector; import android.view.GestureDetector;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.ScaleGestureDetector; import android.view.ScaleGestureDetector;
@ -55,7 +54,7 @@ public class ZoomPanView extends FrameLayout implements ScaleGestureDetector.OnS
private float lastFlingVelocityY; private float lastFlingVelocityY;
private float backgroundAlphaForTransition=1f; private float backgroundAlphaForTransition=1f;
private boolean forceUpdateLayout; private boolean forceUpdateLayout;
private int[] transitionCornerRadius; private float[] transitionCornerRadius;
private Path transitionClipPath=new Path(); private Path transitionClipPath=new Path();
private float[] tmpFloatArray=new float[8]; private float[] tmpFloatArray=new float[8];
@ -72,13 +71,13 @@ public class ZoomPanView extends FrameLayout implements ScaleGestureDetector.OnS
@Override @Override
public void setValue(ZoomPanView object, float value){ public void setValue(ZoomPanView object, float value){
object.rawCropAndFadeValue=value; object.rawCropAndFadeValue=value;
if(value>0.1f) if(value>0.3f)
object.child.setAlpha(Math.min((value-0.1f)/0.4f, 1f)); object.child.setAlpha(Math.min((value-0.3f)/0.2f, 1f));
else else
object.child.setAlpha(0f); object.child.setAlpha(0f);
if(value>0.3f) if(value>0.5f)
object.setCropAnimationValue(Math.min(1f, (value-0.3f)/0.7f)); object.setCropAnimationValue(Math.min(1f, (value-0.5f)/0.5f));
else else
object.setCropAnimationValue(0f); object.setCropAnimationValue(0f);
@ -159,10 +158,10 @@ public class ZoomPanView extends FrameLayout implements ScaleGestureDetector.OnS
canvas.save(); canvas.save();
if(transitionCornerRadius!=null){ if(transitionCornerRadius!=null){
float radiusScale=child.getScaleX(); float radiusScale=child.getScaleX();
tmpFloatArray[0]=tmpFloatArray[1]=(float)transitionCornerRadius[0]*radiusScale*(1f-cropAnimationValue); tmpFloatArray[0]=tmpFloatArray[1]=transitionCornerRadius[0]*radiusScale*(1f-cropAnimationValue);
tmpFloatArray[2]=tmpFloatArray[3]=(float)transitionCornerRadius[1]*radiusScale*(1f-cropAnimationValue); tmpFloatArray[2]=tmpFloatArray[3]=transitionCornerRadius[1]*radiusScale*(1f-cropAnimationValue);
tmpFloatArray[4]=tmpFloatArray[5]=(float)transitionCornerRadius[2]*radiusScale*(1f-cropAnimationValue); tmpFloatArray[4]=tmpFloatArray[5]=transitionCornerRadius[2]*radiusScale*(1f-cropAnimationValue);
tmpFloatArray[6]=tmpFloatArray[7]=(float)transitionCornerRadius[3]*radiusScale*(1f-cropAnimationValue); tmpFloatArray[6]=tmpFloatArray[7]=transitionCornerRadius[3]*radiusScale*(1f-cropAnimationValue);
transitionClipPath.rewind(); transitionClipPath.rewind();
transitionClipPath.addRoundRect(interpolate(tmpRect2.left, tmpRect.left, cropAnimationValue), transitionClipPath.addRoundRect(interpolate(tmpRect2.left, tmpRect.left, cropAnimationValue),
interpolate(tmpRect2.top, tmpRect.top, cropAnimationValue), interpolate(tmpRect2.top, tmpRect.top, cropAnimationValue),
@ -213,12 +212,15 @@ public class ZoomPanView extends FrameLayout implements ScaleGestureDetector.OnS
return initialScale; return initialScale;
} }
private void validateAndSetCornerRadius(int[] cornerRadius){ private void validateAndSetCornerRadius(int[] cornerRadius, float scale){
transitionCornerRadius=null; transitionCornerRadius=null;
if(cornerRadius!=null && cornerRadius.length==4){ if(cornerRadius!=null && cornerRadius.length==4){
for(int corner:cornerRadius){ for(int corner:cornerRadius){
if(corner>0){ if(corner>0){
transitionCornerRadius=cornerRadius; transitionCornerRadius=new float[4];
for(int i=0;i<4;i++){
transitionCornerRadius[i]=cornerRadius[i]*scale;
}
break; break;
} }
} }
@ -240,7 +242,7 @@ public class ZoomPanView extends FrameLayout implements ScaleGestureDetector.OnS
animatingTransition=true; animatingTransition=true;
matrix.getValues(matrixValues); matrix.getValues(matrixValues);
validateAndSetCornerRadius(cornerRadius); validateAndSetCornerRadius(cornerRadius, 1f/initialScale);
child.setAlpha(0f); child.setAlpha(0f);
setupAndStartTransitionAnim(new SpringAnimation(this, CROP_AND_FADE, 1f).setMinimumVisibleChange(DynamicAnimation.MIN_VISIBLE_CHANGE_SCALE)); setupAndStartTransitionAnim(new SpringAnimation(this, CROP_AND_FADE, 1f).setMinimumVisibleChange(DynamicAnimation.MIN_VISIBLE_CHANGE_SCALE));
@ -270,7 +272,7 @@ public class ZoomPanView extends FrameLayout implements ScaleGestureDetector.OnS
animatingTransition=true; animatingTransition=true;
dismissAfterTransition=true; dismissAfterTransition=true;
rawCropAndFadeValue=1f; rawCropAndFadeValue=1f;
validateAndSetCornerRadius(cornerRadius); validateAndSetCornerRadius(cornerRadius, 1f/initialScale);
setupAndStartTransitionAnim(new SpringAnimation(this, CROP_AND_FADE, 0f).setMinimumVisibleChange(DynamicAnimation.MIN_VISIBLE_CHANGE_SCALE)); setupAndStartTransitionAnim(new SpringAnimation(this, CROP_AND_FADE, 0f).setMinimumVisibleChange(DynamicAnimation.MIN_VISIBLE_CHANGE_SCALE));
setupAndStartTransitionAnim(new SpringAnimation(child, DynamicAnimation.SCALE_X, initialScale)); setupAndStartTransitionAnim(new SpringAnimation(child, DynamicAnimation.SCALE_X, initialScale));

View file

@ -1,101 +0,0 @@
package org.joinmastodon.android.ui.utils;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.view.View;
import org.joinmastodon.android.R;
import org.joinmastodon.android.fragments.BaseStatusListFragment;
import org.joinmastodon.android.ui.displayitems.NotificationHeaderStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
import java.util.List;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import me.grishka.appkit.utils.V;
public class InsetStatusItemDecoration extends RecyclerView.ItemDecoration{
private final BaseStatusListFragment<?> listFragment;
private Paint paint=new Paint(Paint.ANTI_ALIAS_FLAG);
private int bgColor;
private int borderColor;
private RectF rect=new RectF();
public InsetStatusItemDecoration(BaseStatusListFragment<?> listFragment){
this.listFragment=listFragment;
bgColor=UiUtils.getThemeColor(listFragment.getActivity(), R.attr.colorM3SurfaceVariant);
borderColor=UiUtils.getThemeColor(listFragment.getActivity(), R.attr.colorM3OutlineVariant);
}
@Override
public void onDraw(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state){
List<StatusDisplayItem> displayItems=listFragment.getDisplayItems();
int pos=0;
for(int i=0; i<parent.getChildCount(); i++){
View child=parent.getChildAt(i);
RecyclerView.ViewHolder holder=parent.getChildViewHolder(child);
pos=holder.getAbsoluteAdapterPosition()-listFragment.getMainAdapterOffset();
boolean inset=holder instanceof StatusDisplayItem.Holder<?> sdi && sdi.getItem() instanceof StatusDisplayItem item && item.inset;
if(inset){
if(rect.isEmpty()){
float childY=child.getY();
if(pos>0 && displayItems.get(pos-1).getType()==StatusDisplayItem.Type.REBLOG_OR_REPLY_LINE){
childY+=V.dp(8);
}
rect.set(child.getX(), i==0 && pos>0 && displayItems.get(pos-1).inset ? V.dp(-10) : childY, child.getX()+child.getWidth(), child.getY()+child.getHeight());
}else{
rect.bottom=Math.max(rect.bottom, child.getY()+child.getHeight());
}
}else if(!rect.isEmpty()){
drawInsetBackground(parent, c);
rect.setEmpty();
}
}
if(!rect.isEmpty()){
if(pos<displayItems.size()-1 && displayItems.get(pos+1).inset){
rect.bottom=parent.getHeight()+V.dp(10);
}
drawInsetBackground(parent, c);
rect.setEmpty();
}
}
private void drawInsetBackground(RecyclerView list, Canvas c){
paint.setStyle(Paint.Style.FILL);
paint.setColor(bgColor);
rect.left=V.dp(16);
rect.right=list.getWidth()-V.dp(16);
c.drawRoundRect(rect, V.dp(4), V.dp(4), paint);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(V.dp(1));
paint.setColor(borderColor);
rect.inset(paint.getStrokeWidth()/2f, paint.getStrokeWidth()/2f);
c.drawRoundRect(rect, V.dp(4), V.dp(4), paint);
}
@Override
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state){
List<StatusDisplayItem> displayItems=listFragment.getDisplayItems();
RecyclerView.ViewHolder holder=parent.getChildViewHolder(view);
if(holder instanceof StatusDisplayItem.Holder<?> sdi && sdi.getItem() instanceof StatusDisplayItem item){
boolean inset=item.inset;
int pos=holder.getAbsoluteAdapterPosition()-listFragment.getMainAdapterOffset();
if(inset){
boolean topSiblingInset=pos>0 && displayItems.get(pos-1).inset;
boolean bottomSiblingInset=pos<displayItems.size()-1 && displayItems.get(pos+1).inset;
StatusDisplayItem.Type type=item.getType();
if(type==StatusDisplayItem.Type.CARD_LARGE || type==StatusDisplayItem.Type.MEDIA_GRID)
outRect.left=outRect.right=V.dp(16);
else
outRect.left=outRect.right=V.dp(8);
if(!bottomSiblingInset)
outRect.bottom=V.dp(16);
if(!topSiblingInset && pos > 1 && displayItems.get(pos-1) instanceof NotificationHeaderStatusDisplayItem)
outRect.top=V.dp(-8);
}
}
}
}

View file

@ -1,20 +1,24 @@
package org.joinmastodon.android.ui.utils; package org.joinmastodon.android.ui.utils;
import android.content.Context; import android.content.Context;
import android.graphics.Outline;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.os.Build; import android.os.Build;
import android.text.TextUtils; import android.text.TextUtils;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewOutlineProvider;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
import org.joinmastodon.android.R; import org.joinmastodon.android.R;
import org.joinmastodon.android.model.Attachment; import org.joinmastodon.android.model.Attachment;
import org.joinmastodon.android.model.Status; import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.ui.PhotoLayoutHelper;
import org.joinmastodon.android.ui.displayitems.MediaGridStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.MediaGridStatusDisplayItem;
import org.joinmastodon.android.ui.drawables.BlurhashCrossfadeDrawable; import org.joinmastodon.android.ui.drawables.BlurhashCrossfadeDrawable;
import org.joinmastodon.android.ui.drawables.PlayIconDrawable; import org.joinmastodon.android.ui.drawables.PlayIconDrawable;
import org.joinmastodon.android.ui.views.MediaGridLayout;
import me.grishka.appkit.utils.V; import me.grishka.appkit.utils.V;
@ -32,6 +36,31 @@ public class MediaAttachmentViewController{
private boolean didClear; private boolean didClear;
private Status status; private Status status;
private Attachment attachment; private Attachment attachment;
private ViewOutlineProvider outlineProvider=new ViewOutlineProvider(){
@Override
public void getOutline(View view, Outline outline){
MediaGridLayout.LayoutParams lp=(MediaGridLayout.LayoutParams) MediaAttachmentViewController.this.view.getLayoutParams();
int mask=lp.tile.getRoundCornersMask();
int radius=V.dp(8);
if(mask==(PhotoLayoutHelper.CORNER_TL | PhotoLayoutHelper.CORNER_TR | PhotoLayoutHelper.CORNER_BL | PhotoLayoutHelper.CORNER_BR)){
outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), radius);
}else if(mask==(PhotoLayoutHelper.CORNER_TL | PhotoLayoutHelper.CORNER_TR)){
outline.setRoundRect(0, 0, view.getWidth(), view.getHeight()+radius, radius);
}else if(mask==(PhotoLayoutHelper.CORNER_BL | PhotoLayoutHelper.CORNER_BR)){
outline.setRoundRect(0, -radius, view.getWidth(), view.getHeight(), radius);
}else if(mask==PhotoLayoutHelper.CORNER_TL){
outline.setRoundRect(0, 0, view.getWidth()+radius, view.getHeight()+radius, radius);
}else if(mask==PhotoLayoutHelper.CORNER_TR){
outline.setRoundRect(-radius, 0, view.getWidth(), view.getHeight()+radius, radius);
}else if(mask==PhotoLayoutHelper.CORNER_BL){
outline.setRoundRect(0, -radius, view.getWidth()+radius, view.getHeight(), radius);
}else if(mask==PhotoLayoutHelper.CORNER_BR){
outline.setRoundRect(-radius, -radius, view.getWidth(), view.getHeight(), radius);
}else{
outline.setRect(0, 0, view.getWidth(), view.getHeight());
}
}
};
public MediaAttachmentViewController(Context context, MediaGridStatusDisplayItem.GridItemType type){ public MediaAttachmentViewController(Context context, MediaGridStatusDisplayItem.GridItemType type){
view=context.getSystemService(LayoutInflater.class).inflate(switch(type){ view=context.getSystemService(LayoutInflater.class).inflate(switch(type){
@ -53,6 +82,12 @@ public class MediaAttachmentViewController{
playButton.setLayerType(View.LAYER_TYPE_SOFTWARE, null); playButton.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
playButton.setBackground(new PlayIconDrawable(context)); playButton.setBackground(new PlayIconDrawable(context));
} }
photo.setOutlineProvider(outlineProvider);
photo.setClipToOutline(true);
if(failedOverlay!=null){
failedOverlay.setOutlineProvider(outlineProvider);
failedOverlay.setClipToOutline(true);
}
} }
public void bind(Attachment attachment, Status status){ public void bind(Attachment attachment, Status status){

View file

@ -14,7 +14,6 @@ import android.widget.TextView;
import org.joinmastodon.android.R; import org.joinmastodon.android.R;
import org.joinmastodon.android.fragments.ProfileFragment; import org.joinmastodon.android.fragments.ProfileFragment;
import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.Card; import org.joinmastodon.android.model.Card;
import org.joinmastodon.android.model.viewmodel.CardViewModel; import org.joinmastodon.android.model.viewmodel.CardViewModel;
import org.joinmastodon.android.ui.OutlineProviders; import org.joinmastodon.android.ui.OutlineProviders;

View file

@ -25,6 +25,7 @@ public class FrameLayoutThatOnlyMeasuresFirstChild extends FrameLayout{
View child0=getChildAt(0); View child0=getChildAt(0);
measureChild(child0, widthMeasureSpec, heightMeasureSpec); measureChild(child0, widthMeasureSpec, heightMeasureSpec);
int vpad=getPaddingTop()+getPaddingBottom(); int vpad=getPaddingTop()+getPaddingBottom();
super.onMeasure(child0.getMeasuredWidth() | MeasureSpec.EXACTLY, (child0.getMeasuredHeight()+vpad) | MeasureSpec.EXACTLY); int hpad=getPaddingLeft()+getPaddingRight();
super.onMeasure((child0.getMeasuredWidth()+hpad) | MeasureSpec.EXACTLY, (child0.getMeasuredHeight()+vpad) | MeasureSpec.EXACTLY);
} }
} }

View file

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

View file

@ -1,23 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape>
<solid android:color="?colorM3Surface"/>
<corners android:radius="20dp"/>
</shape>
</item>
<item>
<scale android:scaleGravity="start|fill_vertical" android:scaleWidth="100%">
<shape>
<solid android:color="@color/poll_option_progress_inset"/>
<corners android:radius="20dp"/>
</shape>
</scale>
</item>
<item>
<shape>
<stroke android:width="1dp" android:color="?colorM3Outline"/>
<corners android:radius="20dp"/>
</shape>
</item>
</layer-list>

View file

@ -1,7 +1,11 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> <layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item> <item>
<color android:color="?colorM3SecondaryContainer"/> <shape>
<solid android:color="?colorM3SecondaryContainer"/>
<stroke android:color="?colorM3OutlineVariant" android:width="1dp"/>
<corners android:radius="8dp"/>
</shape>
</item> </item>
<item android:gravity="left" android:width="5dp"> <item android:gravity="left" android:width="5dp">
<shape> <shape>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="20dp"
android:height="20dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:fillColor="@android:color/white"
android:pathData="M279,899L110,730L279,561L355,637L315,676L694,676L694,520L802,520L802,784L315,784L355,823L279,899ZM158,450L158,186L646,186L606,147L682,71L851,240L682,409L606,333L646,294L266,294L266,450L158,450Z"/>
</vector>

View file

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="20dp"
android:height="20dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:autoMirrored="true">
<path
android:fillColor="@android:color/white"
android:pathData="M732,784L732,606Q732,565 704,537Q676,509 635,509L334,509L434,609L352,692L110,450L352,208L434,291L334,391L635,391Q724,391 787,454Q850,517 850,606L850,784L732,784Z"/>
</vector>

View file

@ -4,6 +4,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingBottom="8dp" android:paddingBottom="8dp"
android:paddingEnd="16dp"
android:clipToPadding="false"> android:clipToPadding="false">
<FrameLayout <FrameLayout

View file

@ -12,19 +12,19 @@
android:id="@+id/reply_btn" android:id="@+id/reply_btn"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="match_parent" android:layout_height="match_parent"
android:paddingLeft="8dp" android:layout_marginEnd="4dp"
android:paddingRight="8dp" android:paddingHorizontal="8dp"
android:background="?android:actionBarItemBackground" android:background="?android:actionBarItemBackground"
android:minWidth="34dp"> android:minWidth="64dp">
<TextView <TextView
android:id="@+id/reply" android:id="@+id/reply"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="24dp" android:layout_height="24dp"
android:layout_gravity="center" android:layout_gravity="center|start"
android:drawableStart="@drawable/ic_reply_20px" android:drawableStart="@drawable/ic_reply_20px"
android:drawablePadding="6dp" android:drawablePadding="6dp"
android:drawableTint="?colorM3OnSurfaceVariant" android:drawableTint="?colorM3Outline"
android:textColor="?colorM3OnSurfaceVariant" android:textColor="?colorM3Outline"
android:gravity="center_vertical" android:gravity="center_vertical"
android:textAppearance="@style/m3_label_medium" android:textAppearance="@style/m3_label_medium"
android:duplicateParentState="true" android:duplicateParentState="true"
@ -32,6 +32,7 @@
</FrameLayout> </FrameLayout>
<Space <Space
android:id="@+id/spacer1"
android:layout_width="0px" android:layout_width="0px"
android:layout_height="1px" android:layout_height="1px"
android:layout_weight="1"/> android:layout_weight="1"/>
@ -40,19 +41,19 @@
android:id="@+id/boost_btn" android:id="@+id/boost_btn"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="match_parent" android:layout_height="match_parent"
android:paddingLeft="8dp" android:layout_marginHorizontal="4dp"
android:paddingRight="8dp" android:paddingHorizontal="8dp"
android:background="?android:actionBarItemBackground" android:background="?android:actionBarItemBackground"
android:minWidth="34dp"> android:minWidth="64dp">
<TextView <TextView
android:id="@+id/boost" android:id="@+id/boost"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="24dp" android:layout_height="24dp"
android:layout_gravity="center" android:layout_gravity="center|start"
android:drawableStart="@drawable/ic_repeat_selector" android:drawableStart="@drawable/ic_repeat_selector"
android:drawablePadding="6dp" android:drawablePadding="6dp"
android:drawableTint="?colorM3OnSurfaceVariant" android:drawableTint="?colorM3Outline"
android:textColor="?colorM3OnSurfaceVariant" android:textColor="?colorM3Outline"
android:gravity="center_vertical" android:gravity="center_vertical"
android:textAppearance="@style/m3_label_medium" android:textAppearance="@style/m3_label_medium"
android:duplicateParentState="true" android:duplicateParentState="true"
@ -60,6 +61,7 @@
</FrameLayout> </FrameLayout>
<Space <Space
android:id="@+id/spacer2"
android:layout_width="0px" android:layout_width="0px"
android:layout_height="1px" android:layout_height="1px"
android:layout_weight="1"/> android:layout_weight="1"/>
@ -68,19 +70,19 @@
android:id="@+id/favorite_btn" android:id="@+id/favorite_btn"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="match_parent" android:layout_height="match_parent"
android:paddingLeft="8dp" android:layout_marginHorizontal="4dp"
android:paddingRight="8dp" android:paddingHorizontal="8dp"
android:background="?android:actionBarItemBackground" android:background="?android:actionBarItemBackground"
android:minWidth="34dp"> android:minWidth="64dp">
<TextView <TextView
android:id="@+id/favorite" android:id="@+id/favorite"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="24dp" android:layout_height="24dp"
android:layout_gravity="center" android:layout_gravity="center|start"
android:drawableStart="@drawable/ic_star_selector" android:drawableStart="@drawable/ic_star_selector"
android:drawablePadding="6dp" android:drawablePadding="6dp"
android:drawableTint="?colorM3OnSurfaceVariant" android:drawableTint="?colorM3Outline"
android:textColor="?colorM3OnSurfaceVariant" android:textColor="?colorM3Outline"
android:gravity="center_vertical" android:gravity="center_vertical"
android:textAppearance="@style/m3_label_medium" android:textAppearance="@style/m3_label_medium"
android:duplicateParentState="true" android:duplicateParentState="true"
@ -96,8 +98,8 @@
android:id="@+id/share_btn" android:id="@+id/share_btn"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="match_parent" android:layout_height="match_parent"
android:paddingLeft="8dp" android:layout_marginStart="4dp"
android:paddingRight="8dp" android:paddingHorizontal="8dp"
android:background="?android:actionBarItemBackground" android:background="?android:actionBarItemBackground"
android:minWidth="34dp"> android:minWidth="34dp">
<ImageView <ImageView
@ -106,7 +108,7 @@
android:layout_height="24dp" android:layout_height="24dp"
android:layout_gravity="center" android:layout_gravity="center"
android:src="@drawable/ic_share_20px" android:src="@drawable/ic_share_20px"
android:tint="?colorM3OnSurfaceVariant" android:tint="?colorM3Outline"
android:gravity="center_vertical"/> android:gravity="center_vertical"/>
</FrameLayout> </FrameLayout>

View file

@ -90,7 +90,7 @@
android:ellipsize="end" android:ellipsize="end"
android:textAppearance="@style/m3_body_medium" android:textAppearance="@style/m3_body_medium"
android:gravity="center_vertical|start" android:gravity="center_vertical|start"
android:textColor="?colorM3OnSurfaceVariant" android:textColor="?colorM3Outline"
android:textAlignment="viewStart" android:textAlignment="viewStart"
tools:text="9h ago · \@Gargron@mastodon.social"/> tools:text="9h ago · \@Gargron@mastodon.social"/>

View file

@ -2,19 +2,20 @@
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingLeft="16dp" android:paddingStart="40dp"
android:paddingRight="16dp" android:paddingEnd="16dp"
android:paddingTop="16dp"> android:paddingTop="12dp"
android:layout_marginBottom="-8dp">
<TextView <TextView
android:id="@+id/text" android:id="@+id/text"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="16dp"
android:textAppearance="@style/m3_label_large" android:textAppearance="@style/m3_label_medium"
android:drawableStart="@drawable/ic_repeat_20px" android:drawableTint="?colorM3Outline"
android:drawableTint="?colorM3OnSurfaceVariant" android:textColor="?colorM3Outline"
android:textColor="?colorM3OnSurfaceVariant" android:drawablePadding="8dp"
android:drawablePadding="6dp" android:gravity="center_vertical"
android:singleLine="true" android:singleLine="true"
android:ellipsize="end"/> android:ellipsize="end"/>