From 5395855775d00f51f114364b25f85b7e341665a6 Mon Sep 17 00:00:00 2001 From: sk Date: Mon, 20 Mar 2023 20:52:20 -0300 Subject: [PATCH] feat: implement notification badge using markers from @sk22. Thank you a lot man! This improvement is amazing --- .../android/PushNotificationReceiver.java | 2 + .../joinmastodon/android/api/ApiUtils.java | 2 +- .../api/requests/markers/GetMarkers.java | 17 +++++ .../android/api/session/AccountSession.java | 2 + .../api/session/AccountSessionManager.java | 20 ++++++ .../events/AllNotificationsSeenEvent.java | 4 ++ .../events/NotificationReceivedEvent.java | 8 +++ .../android/fragments/HomeFragment.java | 65 ++++++++++++++++++- .../fragments/NotificationsListFragment.java | 5 ++ .../joinmastodon/android/model/Marker.java | 9 +++ .../joinmastodon/android/model/Markers.java | 14 ++++ .../ic_fluent_alert_28_filled_badged.xml | 10 +++ .../ic_fluent_alert_28_regular_badged.xml | 10 +++ .../ic_fluent_alert_28_selector_badged.xml | 8 +++ mastodon/src/main/res/layout/tab_bar.xml | 1 - 15 files changed, 174 insertions(+), 3 deletions(-) create mode 100644 mastodon/src/main/java/org/joinmastodon/android/api/requests/markers/GetMarkers.java create mode 100644 mastodon/src/main/java/org/joinmastodon/android/events/AllNotificationsSeenEvent.java create mode 100644 mastodon/src/main/java/org/joinmastodon/android/events/NotificationReceivedEvent.java create mode 100644 mastodon/src/main/java/org/joinmastodon/android/model/Markers.java create mode 100644 mastodon/src/main/res/drawable/ic_fluent_alert_28_filled_badged.xml create mode 100644 mastodon/src/main/res/drawable/ic_fluent_alert_28_regular_badged.xml create mode 100644 mastodon/src/main/res/drawable/ic_fluent_alert_28_selector_badged.xml diff --git a/mastodon/src/main/java/org/joinmastodon/android/PushNotificationReceiver.java b/mastodon/src/main/java/org/joinmastodon/android/PushNotificationReceiver.java index 575431370..24fd0084a 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/PushNotificationReceiver.java +++ b/mastodon/src/main/java/org/joinmastodon/android/PushNotificationReceiver.java @@ -24,6 +24,7 @@ import org.joinmastodon.android.api.requests.statuses.SetStatusFavorited; import org.joinmastodon.android.api.requests.statuses.SetStatusReblogged; import org.joinmastodon.android.api.session.AccountSession; import org.joinmastodon.android.api.session.AccountSessionManager; +import org.joinmastodon.android.events.NotificationReceivedEvent; import org.joinmastodon.android.model.Account; import org.joinmastodon.android.model.NotificationAction; import org.joinmastodon.android.model.Preferences; @@ -84,6 +85,7 @@ public class PushNotificationReceiver extends BroadcastReceiver{ } String accountID=account.getID(); PushNotification pn=AccountSessionManager.getInstance().getAccount(accountID).getPushSubscriptionManager().decryptNotification(k, p, s); + E.post(new NotificationReceivedEvent(pn.notificationId+"")); new GetNotificationByID(pn.notificationId+"") .setCallback(new Callback<>(){ @Override diff --git a/mastodon/src/main/java/org/joinmastodon/android/api/ApiUtils.java b/mastodon/src/main/java/org/joinmastodon/android/api/ApiUtils.java index 8c588e468..a2cb79775 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/api/ApiUtils.java +++ b/mastodon/src/main/java/org/joinmastodon/android/api/ApiUtils.java @@ -11,7 +11,7 @@ public class ApiUtils{ //no instance } - public static > List enumSetToStrings(EnumSet e, Class cls){ +public static > List enumSetToStrings(EnumSet e, Class cls){ return e.stream().map(ev->{ try{ SerializedName annotation=cls.getField(ev.name()).getAnnotation(SerializedName.class); diff --git a/mastodon/src/main/java/org/joinmastodon/android/api/requests/markers/GetMarkers.java b/mastodon/src/main/java/org/joinmastodon/android/api/requests/markers/GetMarkers.java new file mode 100644 index 000000000..b7dd6536b --- /dev/null +++ b/mastodon/src/main/java/org/joinmastodon/android/api/requests/markers/GetMarkers.java @@ -0,0 +1,17 @@ +package org.joinmastodon.android.api.requests.markers; + +import org.joinmastodon.android.api.ApiUtils; +import org.joinmastodon.android.api.MastodonAPIRequest; +import org.joinmastodon.android.model.Marker; +import org.joinmastodon.android.model.Markers; + +import java.util.EnumSet; + +public class GetMarkers extends MastodonAPIRequest { + public GetMarkers(EnumSet timelines) { + super(HttpMethod.GET, "/markers", Markers.class); + for (String type : ApiUtils.enumSetToStrings(timelines, Marker.Type.class)){ + addQueryParameter("timeline[]", type); + } + } +} diff --git a/mastodon/src/main/java/org/joinmastodon/android/api/session/AccountSession.java b/mastodon/src/main/java/org/joinmastodon/android/api/session/AccountSession.java index e7fa19d9e..da32d9d1a 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/api/session/AccountSession.java +++ b/mastodon/src/main/java/org/joinmastodon/android/api/session/AccountSession.java @@ -7,6 +7,7 @@ import org.joinmastodon.android.api.StatusInteractionController; import org.joinmastodon.android.model.Account; import org.joinmastodon.android.model.Application; import org.joinmastodon.android.model.Filter; +import org.joinmastodon.android.model.Markers; import org.joinmastodon.android.model.Preferences; import org.joinmastodon.android.model.PushSubscription; import org.joinmastodon.android.model.Token; @@ -31,6 +32,7 @@ public class AccountSession{ public String pushAccountID; public Preferences preferences; public AccountActivationInfo activationInfo; + public Markers markers; private transient MastodonAPIController apiController; private transient StatusInteractionController statusInteractionController, remoteStatusInteractionController; private transient CacheController cacheController; diff --git a/mastodon/src/main/java/org/joinmastodon/android/api/session/AccountSessionManager.java b/mastodon/src/main/java/org/joinmastodon/android/api/session/AccountSessionManager.java index 2a3dea401..e97b5189c 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/api/session/AccountSessionManager.java +++ b/mastodon/src/main/java/org/joinmastodon/android/api/session/AccountSessionManager.java @@ -25,6 +25,7 @@ import org.joinmastodon.android.api.requests.accounts.GetWordFilters; import org.joinmastodon.android.api.requests.instance.GetCustomEmojis; import org.joinmastodon.android.api.requests.accounts.GetOwnAccount; import org.joinmastodon.android.api.requests.instance.GetInstance; +import org.joinmastodon.android.api.requests.markers.GetMarkers; import org.joinmastodon.android.api.requests.oauth.CreateOAuthApp; import org.joinmastodon.android.events.EmojiUpdatedEvent; import org.joinmastodon.android.model.Account; @@ -33,6 +34,8 @@ import org.joinmastodon.android.model.Emoji; import org.joinmastodon.android.model.EmojiCategory; import org.joinmastodon.android.model.Filter; import org.joinmastodon.android.model.Instance; +import org.joinmastodon.android.model.Marker; +import org.joinmastodon.android.model.Markers; import org.joinmastodon.android.model.Preferences; import org.joinmastodon.android.model.Token; @@ -46,6 +49,7 @@ import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; +import java.util.EnumSet; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -255,6 +259,7 @@ public class AccountSessionManager{ // if(now-session.filtersLastUpdated>3600_000L){ updateSessionWordFilters(session); // } + updateSessionMarkers(session); } if(loadedInstances){ maybeUpdateCustomEmojis(domains); @@ -319,6 +324,21 @@ public class AccountSessionManager{ .exec(session.getID()); } + private void updateSessionMarkers(AccountSession session) { + new GetMarkers(EnumSet.allOf(Marker.Type.class)).setCallback(new Callback<>() { + @Override + public void onSuccess(Markers markers) { + session.markers = markers; + writeAccountsFile(); + } + + @Override + public void onError(ErrorResponse error) { + + } + }).exec(session.getID()); + } + public void updateInstanceInfo(String domain){ new GetInstance() .setCallback(new Callback<>(){ diff --git a/mastodon/src/main/java/org/joinmastodon/android/events/AllNotificationsSeenEvent.java b/mastodon/src/main/java/org/joinmastodon/android/events/AllNotificationsSeenEvent.java new file mode 100644 index 000000000..aded8546a --- /dev/null +++ b/mastodon/src/main/java/org/joinmastodon/android/events/AllNotificationsSeenEvent.java @@ -0,0 +1,4 @@ +package org.joinmastodon.android.events; + +public class AllNotificationsSeenEvent { +} diff --git a/mastodon/src/main/java/org/joinmastodon/android/events/NotificationReceivedEvent.java b/mastodon/src/main/java/org/joinmastodon/android/events/NotificationReceivedEvent.java new file mode 100644 index 000000000..0ea284f07 --- /dev/null +++ b/mastodon/src/main/java/org/joinmastodon/android/events/NotificationReceivedEvent.java @@ -0,0 +1,8 @@ +package org.joinmastodon.android.events; + +public class NotificationReceivedEvent { + public String id; + public NotificationReceivedEvent(String id) { + this.id = id; + } +} diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/HomeFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/HomeFragment.java index 20d38b850..9769cecf2 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/HomeFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/HomeFragment.java @@ -23,19 +23,34 @@ import androidx.annotation.Nullable; import org.joinmastodon.android.DomainManager; import org.joinmastodon.android.GlobalUserPreferences; import org.joinmastodon.android.MainActivity; +import org.joinmastodon.android.E; import org.joinmastodon.android.R; +import org.joinmastodon.android.api.requests.notifications.GetNotifications; import org.joinmastodon.android.api.session.AccountSession; import org.joinmastodon.android.api.session.AccountSessionManager; +import org.joinmastodon.android.events.AllNotificationsSeenEvent; +import org.joinmastodon.android.events.NotificationReceivedEvent; import org.joinmastodon.android.fragments.discover.DiscoverFragment; import org.joinmastodon.android.model.Account; +import org.joinmastodon.android.model.Instance; +import org.joinmastodon.android.model.Notification; import org.joinmastodon.android.ui.AccountSwitcherSheet; import org.joinmastodon.android.ui.utils.UiUtils; import org.joinmastodon.android.ui.views.TabBar; import org.parceler.Parcels; import java.util.ArrayList; +import java.util.EnumSet; +import java.util.List; + +import androidx.annotation.IdRes; +import androidx.annotation.Nullable; + +import com.squareup.otto.Subscribe; import me.grishka.appkit.FragmentStackActivity; +import me.grishka.appkit.api.Callback; +import me.grishka.appkit.api.ErrorResponse; import me.grishka.appkit.fragments.AppKitFragment; import me.grishka.appkit.fragments.LoaderFragment; import me.grishka.appkit.fragments.OnBackPressedListener; @@ -58,7 +73,6 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene private View tabBarWrap; private ImageView tabBarAvatar; private ImageView notificationTabIcon; - private boolean notificationBadged = false; @IdRes private int currentTab=R.id.tab_home; @@ -67,6 +81,7 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene @Override public void onCreate(Bundle savedInstanceState){ super.onCreate(savedInstanceState); + E.register(this); accountID=getArguments().getString("account"); setTitle(R.string.mo_app_name); @@ -124,6 +139,9 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene Account self=AccountSessionManager.getInstance().getAccount(accountID).self; ViewImageLoader.load(tabBarAvatar, null, new UrlImageLoaderRequest(self.avatar, V.dp(28), V.dp(28))); + notificationTabIcon=content.findViewById(R.id.tab_notifications); + updateNotificationBadge(); + if(savedInstanceState==null){ getChildFragmentManager().beginTransaction() .add(R.id.fragment_wrap, homeTabFragment) @@ -344,4 +362,49 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene // getChildFragmentManager().putFragment(outState, "notificationsFragment", notificationsFragment); // getChildFragmentManager().putFragment(outState, "profileFragment", profileFragment); } + + public void updateNotificationBadge() { + AccountSession session = AccountSessionManager.getInstance().getAccount(accountID); + Instance instance = AccountSessionManager.getInstance().getInstanceInfo(session.domain); + + new GetNotifications(null, 1, EnumSet.allOf(Notification.Type.class), instance.pleroma != null) + .setCallback(new Callback<>() { + @Override + public void onSuccess(List notifications) { + if (notifications.size() > 0) { + try { + long newestId = Long.parseLong(notifications.get(0).id); + long lastSeenId = Long.parseLong(session.markers.notifications.lastReadId); + System.out.println("NEWEST: " + newestId); + System.out.println("LAST SEEN: " + lastSeenId); + + setNotificationBadge(newestId > lastSeenId); + } catch (Exception ignored) { + setNotificationBadge(false); + } + } + } + + @Override + public void onError(ErrorResponse error) { + setNotificationBadge(false); + } + }).exec(accountID); + } + + public void setNotificationBadge(boolean badge) { + notificationTabIcon.setImageResource(badge + ? R.drawable.ic_fluent_alert_28_selector_badged + : R.drawable.ic_fluent_alert_28_selector); + } + + @Subscribe + public void onNotificationReceived(NotificationReceivedEvent notificationReceivedEvent) { + setNotificationBadge(true); + } + + @Subscribe + public void onAllNotificationsSeen(AllNotificationsSeenEvent allNotificationsSeenEvent) { + setNotificationBadge(false); + } } 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 548d44469..c5861d760 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/NotificationsListFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/NotificationsListFragment.java @@ -11,6 +11,7 @@ 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.AllNotificationsSeenEvent; import org.joinmastodon.android.events.PollUpdatedEvent; import org.joinmastodon.android.events.RemoveAccountPostsEvent; import org.joinmastodon.android.model.Account; @@ -145,7 +146,11 @@ public class NotificationsListFragment extends BaseStatusListFragment + + + + + + + + + \ No newline at end of file diff --git a/mastodon/src/main/res/drawable/ic_fluent_alert_28_regular_badged.xml b/mastodon/src/main/res/drawable/ic_fluent_alert_28_regular_badged.xml new file mode 100644 index 000000000..831e895fa --- /dev/null +++ b/mastodon/src/main/res/drawable/ic_fluent_alert_28_regular_badged.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/mastodon/src/main/res/drawable/ic_fluent_alert_28_selector_badged.xml b/mastodon/src/main/res/drawable/ic_fluent_alert_28_selector_badged.xml new file mode 100644 index 000000000..3457f1fb5 --- /dev/null +++ b/mastodon/src/main/res/drawable/ic_fluent_alert_28_selector_badged.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/mastodon/src/main/res/layout/tab_bar.xml b/mastodon/src/main/res/layout/tab_bar.xml index 2dcde6dae..34827b3a6 100644 --- a/mastodon/src/main/res/layout/tab_bar.xml +++ b/mastodon/src/main/res/layout/tab_bar.xml @@ -51,7 +51,6 @@ android:scaleType="center" android:contentDescription="@string/notifications" android:background="?android:selectableItemBackgroundBorderless" - android:tint="?android:colorPrimary" android:src="@drawable/ic_fluent_alert_28_selector"/>