diff --git a/mastodon/src/main/java/org/joinmastodon/android/GlobalUserPreferences.java b/mastodon/src/main/java/org/joinmastodon/android/GlobalUserPreferences.java index 9ebcf2e54..bf2f77d4b 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/GlobalUserPreferences.java +++ b/mastodon/src/main/java/org/joinmastodon/android/GlobalUserPreferences.java @@ -26,6 +26,7 @@ public class GlobalUserPreferences{ public static boolean disableMarquee; public static boolean disableSwipe; public static boolean voteButtonForSingleChoice; + public static boolean enableDeleteNotifications; public static ThemePreference theme; public static ColorPreference color; @@ -55,6 +56,7 @@ public class GlobalUserPreferences{ disableMarquee=prefs.getBoolean("disableMarquee", false); disableSwipe=prefs.getBoolean("disableSwipe", false); voteButtonForSingleChoice=prefs.getBoolean("voteButtonForSingleChoice", true); + enableDeleteNotifications=prefs.getBoolean("enableDeleteNotifications", false); theme=ThemePreference.values()[prefs.getInt("theme", 0)]; recentLanguages=fromJson(prefs.getString("recentLanguages", "{}"), recentLanguagesType, new HashMap<>()); @@ -79,6 +81,7 @@ public class GlobalUserPreferences{ .putBoolean("alwaysExpandContentWarnings", alwaysExpandContentWarnings) .putBoolean("disableMarquee", disableMarquee) .putBoolean("disableSwipe", disableSwipe) + .putBoolean("enableDeleteNotifications", enableDeleteNotifications) .putInt("theme", theme.ordinal()) .putString("color", color.name()) .putString("recentLanguages", gson.toJson(recentLanguages)) diff --git a/mastodon/src/main/java/org/joinmastodon/android/api/requests/notifications/DismissNotification.java b/mastodon/src/main/java/org/joinmastodon/android/api/requests/notifications/DismissNotification.java new file mode 100644 index 000000000..ff83a55f0 --- /dev/null +++ b/mastodon/src/main/java/org/joinmastodon/android/api/requests/notifications/DismissNotification.java @@ -0,0 +1,17 @@ +package org.joinmastodon.android.api.requests.notifications; + +import com.google.gson.reflect.TypeToken; + +import org.joinmastodon.android.api.ApiUtils; +import org.joinmastodon.android.api.MastodonAPIRequest; +import org.joinmastodon.android.model.Notification; + +import java.util.EnumSet; +import java.util.List; + +public class DismissNotification extends MastodonAPIRequest{ + public DismissNotification(String id){ + super(HttpMethod.POST, "/notifications/" + (id != null ? id + "/dismiss" : "clear"), Object.class); + setRequestBody(new Object()); + } +} diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/NotificationsFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/NotificationsFragment.java index 0bfc93cad..59aa12995 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/NotificationsFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/NotificationsFragment.java @@ -74,15 +74,25 @@ public class NotificationsFragment extends MastodonToolbarFragment implements Sc @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){ inflater.inflate(R.menu.notifications, menu); + menu.findItem(R.id.clear_notifications).setVisible(GlobalUserPreferences.enableDeleteNotifications); } @Override public boolean onOptionsItemSelected(MenuItem item) { - if (item.getItemId() != R.id.follow_requests) return false; - Bundle args=new Bundle(); - args.putString("account", accountID); - Nav.go(getActivity(), FollowRequestsListFragment.class, args); - return true; + if (item.getItemId() == R.id.follow_requests) { + Bundle args=new Bundle(); + args.putString("account", accountID); + Nav.go(getActivity(), FollowRequestsListFragment.class, args); + return true; + } else if (item.getItemId() == R.id.clear_notifications) { + UiUtils.confirmDeleteNotification(getActivity(), accountID, null, ()->{ + for (int i = 0; i < tabViews.length; i++) { + getFragmentForPage(i).reload(); + } + }); + return true; + } + return false; } @Override 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 c63848b45..bf4936c34 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/NotificationsListFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/NotificationsListFragment.java @@ -2,6 +2,8 @@ package org.joinmastodon.android.fragments; import android.app.Activity; import android.os.Bundle; +import android.view.Menu; +import android.view.MenuInflater; import android.view.View; import com.squareup.otto.Subscribe; @@ -10,7 +12,6 @@ import org.joinmastodon.android.E; import org.joinmastodon.android.R; import org.joinmastodon.android.api.requests.markers.SaveMarkers; import org.joinmastodon.android.api.session.AccountSessionManager; -import org.joinmastodon.android.events.NotificationDeletedEvent; import org.joinmastodon.android.events.PollUpdatedEvent; import org.joinmastodon.android.events.RemoveAccountPostsEvent; import org.joinmastodon.android.model.Notification; @@ -78,9 +79,9 @@ public class NotificationsListFragment extends BaseStatusListFragment getString(R.string.user_favorited); case POLL -> getString(R.string.poll_ended); }; - HeaderStatusDisplayItem titleItem=extraText!=null ? new HeaderStatusDisplayItem(n.id, n.account, n.createdAt, this, accountID, null, extraText) : null; + HeaderStatusDisplayItem titleItem=extraText!=null ? new HeaderStatusDisplayItem(n.id, n.account, n.createdAt, this, accountID, null, extraText, n) : null; if(n.status!=null){ - ArrayList items=StatusDisplayItem.buildItems(this, n.status, accountID, n, knownAccounts, titleItem!=null, titleItem==null); + ArrayList items=StatusDisplayItem.buildItems(this, n.status, accountID, n, knownAccounts, titleItem!=null, titleItem==null, n); if(titleItem!=null){ for(StatusDisplayItem item:items){ if(item instanceof ImageStatusDisplayItem imgItem){ @@ -210,7 +211,7 @@ public class NotificationsListFragment extends BaseStatusListFragment{ + GlobalUserPreferences.showFederatedTimeline=i.checked; + GlobalUserPreferences.save(); + needAppRestart=true; + })); items.add(new SwitchItem(R.string.settings_gif, R.drawable.ic_fluent_gif_24_regular, GlobalUserPreferences.playGifs, i->{ GlobalUserPreferences.playGifs=i.checked; GlobalUserPreferences.save(); @@ -126,6 +131,11 @@ public class SettingsFragment extends MastodonToolbarFragment{ GlobalUserPreferences.save(); needAppRestart=true; })); + items.add(new SwitchItem(R.string.sk_enable_delete_notifications, R.drawable.ic_fluent_delete_24_regular, GlobalUserPreferences.enableDeleteNotifications, i->{ + GlobalUserPreferences.enableDeleteNotifications=i.checked; + GlobalUserPreferences.save(); + needAppRestart=true; + })); items.add(new HeaderItem(R.string.home_timeline)); items.add(new SwitchItem(R.string.sk_settings_show_replies, R.drawable.ic_fluent_chat_multiple_24_regular, GlobalUserPreferences.showReplies, i->{ @@ -140,11 +150,6 @@ public class SettingsFragment extends MastodonToolbarFragment{ GlobalUserPreferences.loadNewPosts=i.checked; GlobalUserPreferences.save(); })); - items.add(new SwitchItem(R.string.sk_settings_show_federated_timeline, R.drawable.ic_fluent_earth_24_regular, GlobalUserPreferences.showFederatedTimeline, i->{ - GlobalUserPreferences.showFederatedTimeline=i.checked; - GlobalUserPreferences.save(); - needAppRestart=true; - })); items.add(new HeaderItem(R.string.settings_notifications)); items.add(notificationPolicyItem=new NotificationPolicyItem()); diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/StatusEditHistoryFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/StatusEditHistoryFragment.java index 73796fc45..63d813d8b 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/StatusEditHistoryFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/StatusEditHistoryFragment.java @@ -54,7 +54,7 @@ public class StatusEditHistoryFragment extends StatusListFragment{ @Override protected List buildDisplayItems(Status s){ - List items=StatusDisplayItem.buildItems(this, s, accountID, s, knownAccounts, true, false); + List items=StatusDisplayItem.buildItems(this, s, accountID, s, knownAccounts, true, false, null); int idx=data.indexOf(s); if(idx>=0){ String date=UiUtils.DATE_TIME_FORMATTER.format(s.createdAt.atZone(ZoneId.systemDefault())); diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/StatusListFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/StatusListFragment.java index dcbeed3c4..98ecfdcda 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/StatusListFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/StatusListFragment.java @@ -29,7 +29,7 @@ public abstract class StatusListFragment extends BaseStatusListFragment{ protected EventListener eventListener=new EventListener(); protected List buildDisplayItems(Status s){ - return StatusDisplayItem.buildItems(this, s, accountID, s, knownAccounts, false, true); + return StatusDisplayItem.buildItems(this, s, accountID, s, knownAccounts, false, true, null); } @Override diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/discover/SearchFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/discover/SearchFragment.java index cfbb2f295..86451742e 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/discover/SearchFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/discover/SearchFragment.java @@ -75,7 +75,7 @@ public class SearchFragment extends BaseStatusListFragment{ return switch(s.type){ case ACCOUNT -> Collections.singletonList(new AccountStatusDisplayItem(s.id, this, s.account)); 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, false, true, null); }; } 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 c7b84c2ec..f71552e56 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 @@ -237,7 +237,7 @@ public class ReportAddPostsChoiceFragment extends StatusListFragment{ @Override protected List buildDisplayItems(Status s){ - List items=StatusDisplayItem.buildItems(this, s, accountID, s, knownAccounts, true, false); + List items=StatusDisplayItem.buildItems(this, s, accountID, s, knownAccounts, true, false, null); for(StatusDisplayItem item:items){ if(item instanceof ImageStatusDisplayItem isdi){ isdi.horizontalInset=V.dp(40+32); 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 885976820..475a0dce1 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 @@ -19,6 +19,8 @@ import android.widget.PopupMenu; import android.widget.TextView; import android.widget.Toast; +import androidx.recyclerview.widget.RecyclerView; + import org.joinmastodon.android.GlobalUserPreferences; import org.joinmastodon.android.R; import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships; @@ -26,11 +28,13 @@ import org.joinmastodon.android.api.requests.statuses.GetStatusSourceText; import org.joinmastodon.android.api.session.AccountSessionManager; import org.joinmastodon.android.fragments.BaseStatusListFragment; import org.joinmastodon.android.fragments.ComposeFragment; +import org.joinmastodon.android.fragments.NotificationsListFragment; import org.joinmastodon.android.fragments.ProfileFragment; import org.joinmastodon.android.fragments.ThreadFragment; import org.joinmastodon.android.fragments.report.ReportReasonChoiceFragment; import org.joinmastodon.android.model.Account; import org.joinmastodon.android.model.Attachment; +import org.joinmastodon.android.model.Notification; import org.joinmastodon.android.model.Relationship; import org.joinmastodon.android.model.Status; import org.joinmastodon.android.ui.text.HtmlParser; @@ -62,8 +66,9 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{ private boolean hasVisibilityToggle; boolean needBottomPadding; private String extraText; + private Notification notification; - public HeaderStatusDisplayItem(String parentID, Account user, Instant createdAt, BaseStatusListFragment parentFragment, String accountID, Status status, String extraText){ + public HeaderStatusDisplayItem(String parentID, Account user, Instant createdAt, BaseStatusListFragment parentFragment, String accountID, Status status, String extraText, Notification notification){ super(parentID, parentFragment); this.user=user; this.createdAt=createdAt; @@ -71,6 +76,7 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{ this.accountID=accountID; parsedName=new SpannableStringBuilder(user.displayName); this.status=status; + this.notification=notification; HtmlParser.parseCustomEmoji(parsedName, user.emojis); emojiHelper.setText(parsedName); if(status!=null){ @@ -107,7 +113,7 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{ public static class Holder extends StatusDisplayItem.Holder implements ImageLoaderViewHolder{ private final TextView name, username, timestamp, extraText; - private final ImageView avatar, more, visibility; + private final ImageView avatar, more, visibility, deleteNotification; private final PopupMenu optionsMenu; private Relationship relationship; private APIRequest currentRelationshipRequest; @@ -127,12 +133,18 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{ avatar=findViewById(R.id.avatar); more=findViewById(R.id.more); visibility=findViewById(R.id.visibility); + deleteNotification=findViewById(R.id.delete_notification); extraText=findViewById(R.id.extra_text); avatar.setOnClickListener(this::onAvaClick); avatar.setOutlineProvider(roundCornersOutline); avatar.setClipToOutline(true); more.setOnClickListener(this::onMoreClick); visibility.setOnClickListener(v->item.parentFragment.onVisibilityIconClick(this)); + deleteNotification.setOnClickListener(v->UiUtils.confirmDeleteNotification(activity, item.parentFragment.getAccountID(), item.notification, ()->{ + if (item.parentFragment instanceof NotificationsListFragment fragment) { + fragment.removeNotification(item.notification); + } + })); optionsMenu=new PopupMenu(activity, more); optionsMenu.inflate(R.menu.post); @@ -226,6 +238,7 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{ 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); + deleteNotification.setVisibility(GlobalUserPreferences.enableDeleteNotifications && item.notification!=null ? View.VISIBLE : View.GONE); if(item.hasVisibilityToggle){ visibility.setImageResource(item.status.spoilerRevealed ? R.drawable.ic_visibility_off : R.drawable.ic_visibility); visibility.setContentDescription(item.parentFragment.getString(item.status.spoilerRevealed ? R.string.hide_content : R.string.reveal_content)); 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 519188806..fdfde9f76 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 @@ -14,6 +14,7 @@ import org.joinmastodon.android.fragments.ThreadFragment; import org.joinmastodon.android.model.Account; import org.joinmastodon.android.model.Attachment; import org.joinmastodon.android.model.DisplayItemsParent; +import org.joinmastodon.android.model.Notification; import org.joinmastodon.android.model.Poll; import org.joinmastodon.android.model.Status; import org.joinmastodon.android.ui.PhotoLayoutHelper; @@ -73,7 +74,7 @@ public abstract class StatusDisplayItem{ }; } - public static ArrayList buildItems(BaseStatusListFragment fragment, Status status, String accountID, DisplayItemsParent parentObject, Map knownAccounts, boolean inset, boolean addFooter){ + public static ArrayList buildItems(BaseStatusListFragment fragment, Status status, String accountID, DisplayItemsParent parentObject, Map knownAccounts, boolean inset, boolean addFooter, Notification notification){ String parentID=parentObject.getID(); ArrayList items=new ArrayList<>(); Status statusForContent=status.getContentStatus(); @@ -92,7 +93,7 @@ public abstract class StatusDisplayItem{ })); } HeaderStatusDisplayItem header; - items.add(header=new HeaderStatusDisplayItem(parentID, statusForContent.account, statusForContent.createdAt, fragment, accountID, statusForContent, null)); + items.add(header=new HeaderStatusDisplayItem(parentID, statusForContent.account, statusForContent.createdAt, fragment, accountID, statusForContent, null, notification)); if(!TextUtils.isEmpty(statusForContent.content)) items.add(new TextStatusDisplayItem(parentID, HtmlParser.parse(statusForContent.content, statusForContent.emojis, statusForContent.mentions, statusForContent.tags, accountID), fragment, statusForContent)); else 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 bea5dacc9..ccf07fd3d 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 @@ -49,6 +49,7 @@ import org.joinmastodon.android.api.requests.accounts.SetAccountMuted; import org.joinmastodon.android.api.requests.accounts.SetDomainBlocked; import org.joinmastodon.android.api.requests.accounts.AuthorizeFollowRequest; import org.joinmastodon.android.api.requests.accounts.RejectFollowRequest; +import org.joinmastodon.android.api.requests.notifications.DismissNotification; import org.joinmastodon.android.api.requests.statuses.DeleteStatus; import org.joinmastodon.android.api.requests.statuses.GetStatusByID; import org.joinmastodon.android.api.requests.statuses.SetStatusPinned; @@ -66,6 +67,7 @@ import org.joinmastodon.android.fragments.ThreadFragment; import org.joinmastodon.android.model.Account; import org.joinmastodon.android.model.Emoji; import org.joinmastodon.android.model.ListTimeline; +import org.joinmastodon.android.model.Notification; import org.joinmastodon.android.model.Relationship; import org.joinmastodon.android.model.Status; import org.joinmastodon.android.ui.M3AlertDialogBuilder; @@ -463,6 +465,25 @@ public class UiUtils{ ); } + public static void confirmDeleteNotification(Activity activity, String accountID, Notification notification, Runnable callback) { + showConfirmationAlert(activity, + notification == null ? R.string.sk_delete_all_notifications : R.string.sk_delete_notification, + notification == null ? R.string.sk_delete_all_notifications_confirm : R.string.sk_delete_notification_confirm, + notification == null ? R.string.sk_delete_all_notifications_confirm_action : R.string.sk_delete_notification_confirm_action, + ()-> new DismissNotification(notification != null ? notification.id : null).setCallback(new Callback<>() { + @Override + public void onSuccess(Object o) { + callback.run(); + } + + @Override + public void onError(ErrorResponse error) { + error.showToast(activity); + } + }).exec(accountID) + ); + } + public static void setRelationshipToActionButton(Relationship relationship, Button button){ setRelationshipToActionButton(relationship, button, false); } diff --git a/mastodon/src/main/res/drawable/ic_fluent_delete_20_filled.xml b/mastodon/src/main/res/drawable/ic_fluent_delete_20_filled.xml new file mode 100644 index 000000000..ffad611d7 --- /dev/null +++ b/mastodon/src/main/res/drawable/ic_fluent_delete_20_filled.xml @@ -0,0 +1,3 @@ + + + diff --git a/mastodon/src/main/res/layout/display_item_header.xml b/mastodon/src/main/res/layout/display_item_header.xml index 9529d4c7b..da788deb6 100644 --- a/mastodon/src/main/res/layout/display_item_header.xml +++ b/mastodon/src/main/res/layout/display_item_header.xml @@ -22,12 +22,26 @@ android:tint="?android:textColorSecondary" /> + + + \ No newline at end of file diff --git a/mastodon/src/main/res/values/strings_sk.xml b/mastodon/src/main/res/values/strings_sk.xml index 113aca6c0..12276107e 100644 --- a/mastodon/src/main/res/values/strings_sk.xml +++ b/mastodon/src/main/res/values/strings_sk.xml @@ -70,4 +70,11 @@ Rules About the app Donate + Delete notification + Delete notification + Are you sure you want to delete this notification? + Delete all notifications + Delete all + Are you sure you want to delete this notification? + Enable deleting notifications