From b44e3424e30f9420e0758e58c382ecbffac6ecfa Mon Sep 17 00:00:00 2001 From: Grishka Date: Mon, 29 Aug 2022 00:12:15 +0300 Subject: [PATCH 1/6] Fix #249 --- .../android/fragments/ComposeFragment.java | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/ComposeFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/ComposeFragment.java index 3ca595a86..da9c960be 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/ComposeFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/ComposeFragment.java @@ -433,9 +433,10 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr @Override public void afterTextChanged(Editable s){ - updateCharCounter(s); + updateCharCounter(); } }); + spoilerEdit.addTextChangedListener(new SimpleTextWatcher(e->updateCharCounter())); if(replyTo!=null){ replyText.setText(getString(R.string.in_reply_to, replyTo.account.displayName)); ArrayList mentions=new ArrayList<>(); @@ -523,7 +524,9 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr } @SuppressLint("NewApi") - private void updateCharCounter(CharSequence text){ + private void updateCharCounter(){ + CharSequence text=mainEditText.getText(); + String countableText=TwitterTextEmojiRegex.VALID_EMOJI_PATTERN.matcher( MENTION_PATTERN.matcher( URL_PATTERN.matcher(text).replaceAll("$2xxxxxxxxxxxxxxxxxxxxxxx") @@ -535,6 +538,9 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr charCount++; } + if(hasSpoiler){ + charCount+=spoilerEdit.length(); + } charCounter.setText(String.valueOf(charLimit-charCount)); trimmedCharCount=text.toString().trim().length(); updatePublishButtonState(); @@ -1019,6 +1025,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr spoilerEdit.setText(""); spoilerBtn.setSelected(false); mainEditText.requestFocus(); + updateCharCounter(); } } From ba3219d9fcc27bfd2986790b029c045cd2f7e959 Mon Sep 17 00:00:00 2001 From: Grishka Date: Mon, 29 Aug 2022 00:57:09 +0300 Subject: [PATCH 2/6] Fix #209, fix #198 --- .../android/api/requests/accounts/GetAccountStatuses.java | 4 +++- .../android/fragments/BaseStatusListFragment.java | 2 +- .../fragments/report/ReportAddPostsChoiceFragment.java | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/mastodon/src/main/java/org/joinmastodon/android/api/requests/accounts/GetAccountStatuses.java b/mastodon/src/main/java/org/joinmastodon/android/api/requests/accounts/GetAccountStatuses.java index 1668ef9d0..1a02d18f2 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/api/requests/accounts/GetAccountStatuses.java +++ b/mastodon/src/main/java/org/joinmastodon/android/api/requests/accounts/GetAccountStatuses.java @@ -26,6 +26,7 @@ public class GetAccountStatuses extends MastodonAPIRequest>{ addQueryParameter("exclude_replies", "true"); addQueryParameter("exclude_reblogs", "true"); } + case OWN_POSTS_AND_REPLIES -> addQueryParameter("exclude_reblogs", "true"); } } @@ -33,6 +34,7 @@ public class GetAccountStatuses extends MastodonAPIRequest>{ DEFAULT, INCLUDE_REPLIES, MEDIA, - NO_REBLOGS + NO_REBLOGS, + OWN_POSTS_AND_REPLIES } } 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 7cc820f98..11bae0e10 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/BaseStatusListFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/BaseStatusListFragment.java @@ -457,7 +457,7 @@ public abstract class BaseStatusListFragment exten status.spoilerRevealed=true; TextStatusDisplayItem.Holder text=findHolderOfType(itemID, TextStatusDisplayItem.Holder.class); if(text!=null) - adapter.notifyItemChanged(text.getAbsoluteAdapterPosition()+getMainAdapterOffset()); + adapter.notifyItemChanged(text.getAbsoluteAdapterPosition()-getMainAdapterOffset()); HeaderStatusDisplayItem.Holder header=findHolderOfType(itemID, HeaderStatusDisplayItem.Holder.class); if(header!=null) header.rebind(); diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/report/ReportAddPostsChoiceFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/report/ReportAddPostsChoiceFragment.java index 61377f2c5..9a37ce8c1 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/report/ReportAddPostsChoiceFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/report/ReportAddPostsChoiceFragment.java @@ -85,7 +85,7 @@ public class ReportAddPostsChoiceFragment extends StatusListFragment{ @Override protected void doLoadData(int offset, int count){ - currentRequest=new GetAccountStatuses(reportAccount.id, offset>0 ? getMaxID() : null, null, count, GetAccountStatuses.Filter.NO_REBLOGS) + currentRequest=new GetAccountStatuses(reportAccount.id, offset>0 ? getMaxID() : null, null, count, GetAccountStatuses.Filter.OWN_POSTS_AND_REPLIES) .setCallback(new SimpleCallback<>(this){ @Override public void onSuccess(List result){ From 265b2ad32cdf4255e5d8c5db6e3f2fcd9f38f6f0 Mon Sep 17 00:00:00 2001 From: Grishka Date: Mon, 29 Aug 2022 21:21:08 +0300 Subject: [PATCH 3/6] Fix #218 --- .../android/ui/displayitems/StatusDisplayItem.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/StatusDisplayItem.java b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/StatusDisplayItem.java index 8feaa1dc8..d1e5e1fd8 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/StatusDisplayItem.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/StatusDisplayItem.java @@ -8,6 +8,7 @@ import android.view.ViewGroup; import org.joinmastodon.android.R; import org.joinmastodon.android.fragments.BaseStatusListFragment; +import org.joinmastodon.android.fragments.ThreadFragment; import org.joinmastodon.android.model.Account; import org.joinmastodon.android.model.Attachment; import org.joinmastodon.android.model.DisplayItemsParent; @@ -114,7 +115,7 @@ public abstract class StatusDisplayItem{ } if(addFooter){ items.add(new FooterStatusDisplayItem(parentID, fragment, statusForContent, accountID)); - if(status.hasGapAfter) + if(status.hasGapAfter && !(fragment instanceof ThreadFragment)) items.add(new GapStatusDisplayItem(parentID, fragment)); } int i=1; From 8fb2b454dd3c423fc0bbefd2cd85e8688ace179d Mon Sep 17 00:00:00 2001 From: Grishka Date: Fri, 2 Sep 2022 02:00:25 +0300 Subject: [PATCH 4/6] Post edit history + extended footer redesign --- .../statuses/GetStatusEditHistory.java | 33 ++++ .../fragments/BaseStatusListFragment.java | 4 + .../fragments/NotificationsListFragment.java | 97 +---------- .../fragments/StatusEditHistoryFragment.java | 157 ++++++++++++++++++ .../joinmastodon/android/model/Status.java | 1 + .../ExtendedFooterStatusDisplayItem.java | 41 +++-- .../displayitems/HeaderStatusDisplayItem.java | 5 +- .../ui/utils/InsetStatusItemDecoration.java | 116 +++++++++++++ .../android/ui/utils/UiUtils.java | 19 +++ .../drawable/ic_fluent_edit_24_regular.xml | 3 + .../layout/display_item_extended_footer.xml | 155 +++++++++++++---- mastodon/src/main/res/values/strings.xml | 28 ++++ 12 files changed, 517 insertions(+), 142 deletions(-) create mode 100644 mastodon/src/main/java/org/joinmastodon/android/api/requests/statuses/GetStatusEditHistory.java create mode 100644 mastodon/src/main/java/org/joinmastodon/android/fragments/StatusEditHistoryFragment.java create mode 100644 mastodon/src/main/java/org/joinmastodon/android/ui/utils/InsetStatusItemDecoration.java create mode 100644 mastodon/src/main/res/drawable/ic_fluent_edit_24_regular.xml diff --git a/mastodon/src/main/java/org/joinmastodon/android/api/requests/statuses/GetStatusEditHistory.java b/mastodon/src/main/java/org/joinmastodon/android/api/requests/statuses/GetStatusEditHistory.java new file mode 100644 index 000000000..e682cd02a --- /dev/null +++ b/mastodon/src/main/java/org/joinmastodon/android/api/requests/statuses/GetStatusEditHistory.java @@ -0,0 +1,33 @@ +package org.joinmastodon.android.api.requests.statuses; + +import com.google.gson.reflect.TypeToken; + +import org.joinmastodon.android.api.MastodonAPIRequest; +import org.joinmastodon.android.model.Status; +import org.joinmastodon.android.model.StatusPrivacy; + +import java.io.IOException; +import java.util.Collections; +import java.util.List; + +import okhttp3.Response; + +public class GetStatusEditHistory extends MastodonAPIRequest>{ + public GetStatusEditHistory(String id){ + super(HttpMethod.GET, "/statuses/"+id+"/history", new TypeToken<>(){}); + } + + @Override + public void validateAndPostprocessResponse(List respObj, Response httpResponse) throws IOException{ + int i=0; + for(Status s:respObj){ + s.uri=""; + s.id="fakeID"+i; + s.visibility=StatusPrivacy.PUBLIC; + s.mentions=Collections.emptyList(); + s.tags=Collections.emptyList(); + i++; + } + super.validateAndPostprocessResponse(respObj, httpResponse); + } +} 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 11bae0e10..575481a05 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/BaseStatusListFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/BaseStatusListFragment.java @@ -579,6 +579,10 @@ public abstract class BaseStatusListFragment exten return true; } + public ArrayList getDisplayItems(){ + return displayItems; + } + @Override public void onApplyWindowInsets(WindowInsets insets){ if(Build.VERSION.SDK_INT>=29 && insets.getTappableElementInsets().bottom==0 && wantsOverlaySystemNavigation()){ diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/NotificationsListFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/NotificationsListFragment.java index 105ececae..073fd66ac 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/NotificationsListFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/NotificationsListFragment.java @@ -1,10 +1,6 @@ package org.joinmastodon.android.fragments; import android.app.Activity; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.Rect; -import android.graphics.RectF; import android.os.Bundle; import android.view.View; @@ -16,15 +12,12 @@ import org.joinmastodon.android.api.session.AccountSessionManager; import org.joinmastodon.android.events.PollUpdatedEvent; import org.joinmastodon.android.model.Notification; import org.joinmastodon.android.model.PaginatedResponse; -import org.joinmastodon.android.model.Poll; import org.joinmastodon.android.model.Status; -import org.joinmastodon.android.ui.PhotoLayoutHelper; import org.joinmastodon.android.ui.displayitems.AccountCardStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.HeaderStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.ImageStatusDisplayItem; -import org.joinmastodon.android.ui.displayitems.LinkCardStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.StatusDisplayItem; -import org.joinmastodon.android.ui.utils.UiUtils; +import org.joinmastodon.android.ui.utils.InsetStatusItemDecoration; import org.parceler.Parcels; import java.util.ArrayList; @@ -33,7 +26,6 @@ import java.util.List; import java.util.Set; import java.util.stream.Collectors; -import androidx.annotation.NonNull; import androidx.recyclerview.widget.RecyclerView; import me.grishka.appkit.Nav; import me.grishka.appkit.api.SimpleCallback; @@ -160,91 +152,7 @@ public class NotificationsListFragment extends BaseStatusListFragment sdi) && sdi.getItem().inset; - if(inset){ - if(rect.isEmpty()){ - rect.set(child.getX(), i==0 && pos>0 && displayItems.get(pos-1).inset ? V.dp(-10) : child.getY(), child.getX()+child.getWidth(), child.getY()+child.getHeight()); - }else{ - rect.bottom=Math.max(rect.bottom, child.getY()+child.getHeight()); - } - }else if(!rect.isEmpty()){ - drawInsetBackground(c); - rect.setEmpty(); - } - } - if(!rect.isEmpty()){ - if(pos sdi){ - boolean inset=sdi.getItem().inset; - int pos=holder.getAbsoluteAdapterPosition(); - if(inset){ - boolean topSiblingInset=pos>0 && displayItems.get(pos-1).inset; - boolean bottomSiblingInset=pos img){ - PhotoLayoutHelper.TiledLayoutResult layout=img.getItem().tiledLayout; - PhotoLayoutHelper.TiledLayoutResult.Tile tile=img.getItem().thisTile; - // only inset those items that are on the edges of the layout - insetLeft=tile.startCol==0; - insetRight=tile.startCol+tile.colSpan==layout.columnSizes.length; - // inset all items in the bottom row - if(tile.startRow+tile.rowSpan==layout.rowSizes.length) - bottomSiblingInset=false; - } - if(insetLeft) - outRect.left=pad; - if(insetRight) - outRect.right=pad; - if(!topSiblingInset) - outRect.top=pad; - if(!bottomSiblingInset) - outRect.bottom=pad; - } - } - } - }); + list.addItemDecoration(new InsetStatusItemDecoration(this)); } private Notification getNotificationByID(String id){ @@ -268,4 +176,5 @@ public class NotificationsListFragment extends BaseStatusListFragment(this){ + @Override + public void onSuccess(List result){ + Collections.sort(result, Comparator.comparing((Status s)->s.createdAt).reversed()); + onDataLoaded(result, false); + } + }) + .exec(accountID); + } + + @Override + protected List buildDisplayItems(Status s){ + List items=StatusDisplayItem.buildItems(this, s, accountID, s, knownAccounts, true, false); + int idx=data.indexOf(s); + if(idx>=0){ + String date=UiUtils.DATE_TIME_FORMATTER.format(s.createdAt.atZone(ZoneId.systemDefault())); + String action=""; + if(idx==data.size()-1){ + action=getString(R.string.edit_original_post); + }else{ + enum StatusEditChangeType{ + TEXT_CHANGED, + SPOILER_ADDED, + SPOILER_REMOVED, + SPOILER_CHANGED, + POLL_ADDED, + POLL_REMOVED, + POLL_CHANGED, + MEDIA_ADDED, + MEDIA_REMOVED, + MEDIA_REORDERED, + MARKED_SENSITIVE, + MARKED_NOT_SENSITIVE + } + EnumSet changes=EnumSet.noneOf(StatusEditChangeType.class); + Status prev=data.get(idx+1); + + if(!Objects.equals(s.content, prev.content)){ + changes.add(StatusEditChangeType.TEXT_CHANGED); + } + if(!Objects.equals(s.spoilerText, prev.spoilerText)){ + if(s.spoilerText==null){ + changes.add(StatusEditChangeType.SPOILER_REMOVED); + }else if(prev.spoilerText==null){ + changes.add(StatusEditChangeType.SPOILER_ADDED); + }else{ + changes.add(StatusEditChangeType.SPOILER_CHANGED); + } + } + if(s.poll!=null || prev.poll!=null){ + if(s.poll==null){ + changes.add(StatusEditChangeType.POLL_REMOVED); + }else if(prev.poll==null){ + changes.add(StatusEditChangeType.POLL_ADDED); + }else if(!s.poll.id.equals(prev.poll.id)){ + changes.add(StatusEditChangeType.POLL_CHANGED); + } + } + List newAttachmentIDs=s.mediaAttachments.stream().map(att->att.id).collect(Collectors.toList()); + List prevAttachmentIDs=s.mediaAttachments.stream().map(att->att.id).collect(Collectors.toList()); + boolean addedOrRemoved=false; + if(!newAttachmentIDs.containsAll(prevAttachmentIDs)){ + changes.add(StatusEditChangeType.MEDIA_REMOVED); + addedOrRemoved=true; + } + if(!prevAttachmentIDs.containsAll(newAttachmentIDs)){ + changes.add(StatusEditChangeType.MEDIA_ADDED); + addedOrRemoved=true; + } + if(!addedOrRemoved && !newAttachmentIDs.equals(prevAttachmentIDs)){ + changes.add(StatusEditChangeType.MEDIA_REORDERED); + } + if(s.sensitive && !prev.sensitive){ + changes.add(StatusEditChangeType.MARKED_SENSITIVE); + }else if(prev.sensitive && !s.sensitive){ + changes.add(StatusEditChangeType.MARKED_NOT_SENSITIVE); + } + + if(changes.size()==1){ + action=getString(switch(changes.iterator().next()){ + case TEXT_CHANGED -> R.string.edit_text_edited; + case SPOILER_ADDED -> R.string.edit_spoiler_added; + case SPOILER_REMOVED -> R.string.edit_spoiler_removed; + case SPOILER_CHANGED -> R.string.edit_spoiler_edited; + case POLL_ADDED -> R.string.edit_poll_added; + case POLL_REMOVED -> R.string.edit_poll_removed; + case POLL_CHANGED -> R.string.edit_poll_edited; + case MEDIA_ADDED -> R.string.edit_media_added; + case MEDIA_REMOVED -> R.string.edit_media_removed; + case MEDIA_REORDERED -> R.string.edit_media_reordered; + case MARKED_SENSITIVE -> R.string.edit_marked_sensitive; + case MARKED_NOT_SENSITIVE -> R.string.edit_marked_not_sensitive; + }); + }else{ + action=getString(R.string.edit_multiple_changed); + } + } + items.add(0, new ReblogOrReplyLineStatusDisplayItem(s.id, this, action+" ยท "+date, Collections.emptyList(), 0)); + } + return items; + } + + @Override + public void onViewCreated(View view, Bundle savedInstanceState){ + super.onViewCreated(view, savedInstanceState); + list.addItemDecoration(new InsetStatusItemDecoration(this)); + } + + @Override + public boolean isItemEnabled(String id){ + return false; + } +} diff --git a/mastodon/src/main/java/org/joinmastodon/android/model/Status.java b/mastodon/src/main/java/org/joinmastodon/android/model/Status.java index aa6459dfe..a531baa0f 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/model/Status.java +++ b/mastodon/src/main/java/org/joinmastodon/android/model/Status.java @@ -37,6 +37,7 @@ public class Status extends BaseModel implements DisplayItemsParent{ public int reblogsCount; public int favouritesCount; public int repliesCount; + public Instant editedAt; public String url; public String inReplyToId; diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/ExtendedFooterStatusDisplayItem.java b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/ExtendedFooterStatusDisplayItem.java index 895a0119e..c21ee992f 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/ExtendedFooterStatusDisplayItem.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/ExtendedFooterStatusDisplayItem.java @@ -1,5 +1,6 @@ package org.joinmastodon.android.ui.displayitems; +import android.annotation.SuppressLint; import android.content.Context; import android.os.Bundle; import android.text.SpannableStringBuilder; @@ -12,6 +13,7 @@ import android.widget.TextView; import org.joinmastodon.android.R; import org.joinmastodon.android.fragments.BaseStatusListFragment; +import org.joinmastodon.android.fragments.StatusEditHistoryFragment; import org.joinmastodon.android.fragments.account_list.StatusFavoritesListFragment; import org.joinmastodon.android.fragments.account_list.StatusReblogsListFragment; import org.joinmastodon.android.fragments.account_list.StatusRelatedAccountListFragment; @@ -43,39 +45,35 @@ public class ExtendedFooterStatusDisplayItem extends StatusDisplayItem{ } public static class Holder extends StatusDisplayItem.Holder{ - private final TextView reblogs, favorites, time; - private final View buttonsView; + private final TextView time, favoritesCount, reblogsCount, lastEditTime; + private final View favorites, reblogs, editHistory; public Holder(Context context, ViewGroup parent){ super(context, R.layout.display_item_extended_footer, parent); reblogs=findViewById(R.id.reblogs); favorites=findViewById(R.id.favorites); + editHistory=findViewById(R.id.edit_history); time=findViewById(R.id.timestamp); - buttonsView=findViewById(R.id.button_bar); + favoritesCount=findViewById(R.id.favorites_count); + reblogsCount=findViewById(R.id.reblogs_count); + lastEditTime=findViewById(R.id.last_edited); reblogs.setOnClickListener(v->startAccountListFragment(StatusReblogsListFragment.class)); favorites.setOnClickListener(v->startAccountListFragment(StatusFavoritesListFragment.class)); + editHistory.setOnClickListener(v->startEditHistoryFragment()); } + @SuppressLint("DefaultLocale") @Override public void onBind(ExtendedFooterStatusDisplayItem item){ Status s=item.status; - if(s.favouritesCount>0){ - favorites.setVisibility(View.VISIBLE); - favorites.setText(getFormattedPlural(R.plurals.x_favorites, s.favouritesCount)); + favoritesCount.setText(String.format("%,d", s.favouritesCount)); + reblogsCount.setText(String.format("%,d", s.reblogsCount)); + if(s.editedAt!=null){ + editHistory.setVisibility(View.VISIBLE); + lastEditTime.setText(item.parentFragment.getString(R.string.last_edit_at_x, UiUtils.formatRelativeTimestampAsMinutesAgo(itemView.getContext(), s.editedAt))); }else{ - favorites.setVisibility(View.GONE); - } - if(s.reblogsCount>0){ - reblogs.setVisibility(View.VISIBLE); - reblogs.setText(getFormattedPlural(R.plurals.x_reblogs, s.reblogsCount)); - }else{ - reblogs.setVisibility(View.GONE); - } - if(s.favouritesCount==0 && s.reblogsCount==0){ - buttonsView.setVisibility(View.GONE); - }else{ - buttonsView.setVisibility(View.VISIBLE); + editHistory.setVisibility(View.GONE); } String timeStr=TIME_FORMATTER.format(item.status.createdAt.atZone(ZoneId.systemDefault())); if(item.status.application!=null && !TextUtils.isEmpty(item.status.application.name)){ @@ -108,5 +106,12 @@ public class ExtendedFooterStatusDisplayItem extends StatusDisplayItem{ args.putParcelable("status", Parcels.wrap(item.status)); Nav.go(item.parentFragment.getActivity(), cls, args); } + + private void startEditHistoryFragment(){ + Bundle args=new Bundle(); + args.putString("account", item.parentFragment.getAccountID()); + args.putString("id", item.status.id); + Nav.go(item.parentFragment.getActivity(), StatusEditHistoryFragment.class, args); + } } } diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/HeaderStatusDisplayItem.java b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/HeaderStatusDisplayItem.java index 468abe32f..1dec009a3 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/HeaderStatusDisplayItem.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/HeaderStatusDisplayItem.java @@ -175,7 +175,10 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{ public void onBind(HeaderStatusDisplayItem item){ name.setText(item.parsedName); username.setText('@'+item.user.acct); - timestamp.setText(UiUtils.formatRelativeTimestamp(itemView.getContext(), item.createdAt)); + if(item.status==null || item.status.editedAt==null) + timestamp.setText(UiUtils.formatRelativeTimestamp(itemView.getContext(), item.createdAt)); + else + timestamp.setText(item.parentFragment.getString(R.string.edited_timestamp, UiUtils.formatRelativeTimestamp(itemView.getContext(), item.status.editedAt))); visibility.setVisibility(item.hasVisibilityToggle && !item.inset ? View.VISIBLE : View.GONE); if(item.hasVisibilityToggle){ visibility.setImageResource(item.status.spoilerRevealed ? R.drawable.ic_visibility_off : R.drawable.ic_visibility); diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/utils/InsetStatusItemDecoration.java b/mastodon/src/main/java/org/joinmastodon/android/ui/utils/InsetStatusItemDecoration.java new file mode 100644 index 000000000..a811fb464 --- /dev/null +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/utils/InsetStatusItemDecoration.java @@ -0,0 +1,116 @@ +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.fragments.NotificationsListFragment; +import org.joinmastodon.android.ui.PhotoLayoutHelper; +import org.joinmastodon.android.ui.displayitems.ImageStatusDisplayItem; +import org.joinmastodon.android.ui.displayitems.LinkCardStatusDisplayItem; +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(), android.R.attr.colorBackground); + borderColor=UiUtils.getThemeColor(listFragment.getActivity(), R.attr.colorPollVoted); + } + + @Override + public void onDraw(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state){ + List displayItems=listFragment.getDisplayItems(); + int pos=0; + for(int i=0; i sdi) && sdi.getItem().inset; + if(inset){ + if(rect.isEmpty()){ + rect.set(child.getX(), i==0 && pos>0 && displayItems.get(pos-1).inset ? V.dp(-10) : child.getY(), 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=listFragment.getDisplayItems(); + RecyclerView.ViewHolder holder=parent.getChildViewHolder(view); + if(holder instanceof StatusDisplayItem.Holder sdi){ + boolean inset=sdi.getItem().inset; + int pos=holder.getAbsoluteAdapterPosition(); + if(inset){ + boolean topSiblingInset=pos>0 && displayItems.get(pos-1).inset; + boolean bottomSiblingInset=pos img){ + PhotoLayoutHelper.TiledLayoutResult layout=img.getItem().tiledLayout; + PhotoLayoutHelper.TiledLayoutResult.Tile tile=img.getItem().thisTile; + // only inset those items that are on the edges of the layout + insetLeft=tile.startCol==0; + insetRight=tile.startCol+tile.colSpan==layout.columnSizes.length; + // inset all items in the bottom row + if(tile.startRow+tile.rowSpan==layout.rowSizes.length) + bottomSiblingInset=false; + } + if(insetLeft) + outRect.left=pad; + if(insetRight) + outRect.right=pad; + if(!topSiblingInset) + outRect.top=pad; + if(!bottomSiblingInset) + outRect.bottom=pad; + } + } + } +} diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/utils/UiUtils.java b/mastodon/src/main/java/org/joinmastodon/android/ui/utils/UiUtils.java index e40a4a7eb..6a5721023 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/utils/UiUtils.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/utils/UiUtils.java @@ -62,6 +62,7 @@ import java.time.Instant; import java.time.ZoneId; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; +import java.time.format.FormatStyle; import java.util.Arrays; import java.util.List; import java.util.Map; @@ -85,6 +86,7 @@ import okhttp3.MediaType; public class UiUtils{ private static Handler mainHandler=new Handler(Looper.getMainLooper()); private static final DateTimeFormatter DATE_FORMATTER_SHORT_WITH_YEAR=DateTimeFormatter.ofPattern("d MMM uuuu"), DATE_FORMATTER_SHORT=DateTimeFormatter.ofPattern("d MMM"); + public static final DateTimeFormatter DATE_TIME_FORMATTER=DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG, FormatStyle.SHORT); private UiUtils(){} @@ -129,6 +131,23 @@ public class UiUtils{ } } + public static String formatRelativeTimestampAsMinutesAgo(Context context, Instant instant){ + long t=instant.toEpochMilli(); + long now=System.currentTimeMillis(); + long diff=now-t; + if(diff<1000L){ + return context.getString(R.string.time_just_now); + }else if(diff<60_000L){ + int secs=(int)(diff/1000L); + return context.getResources().getQuantityString(R.plurals.x_seconds_ago, secs, secs); + }else if(diff<3600_000L){ + int mins=(int)(diff/60_000L); + return context.getResources().getQuantityString(R.plurals.x_minutes_ago, mins, mins); + }else{ + return DATE_TIME_FORMATTER.format(instant.atZone(ZoneId.systemDefault())); + } + } + public static String formatTimeLeft(Context context, Instant instant){ long t=instant.toEpochMilli(); long now=System.currentTimeMillis(); diff --git a/mastodon/src/main/res/drawable/ic_fluent_edit_24_regular.xml b/mastodon/src/main/res/drawable/ic_fluent_edit_24_regular.xml new file mode 100644 index 000000000..8e828fbfa --- /dev/null +++ b/mastodon/src/main/res/drawable/ic_fluent_edit_24_regular.xml @@ -0,0 +1,3 @@ + + + diff --git a/mastodon/src/main/res/layout/display_item_extended_footer.xml b/mastodon/src/main/res/layout/display_item_extended_footer.xml index 9f292a5e4..0ffb41f21 100644 --- a/mastodon/src/main/res/layout/display_item_extended_footer.xml +++ b/mastodon/src/main/res/layout/display_item_extended_footer.xml @@ -2,49 +2,146 @@ + android:layout_height="wrap_content"> - + android:layout_height="64dp" + android:paddingLeft="16dp" + android:paddingRight="16dp" + android:paddingTop="12dp" + android:paddingBottom="12dp" + android:background="?android:selectableItemBackground"> -