From e2be4871e0eed838201ee970ef936b9af6bb5f2d Mon Sep 17 00:00:00 2001 From: Thomas Date: Sat, 1 Oct 2022 17:00:03 +0200 Subject: [PATCH] Add counter for new messages --- .../app/fedilab/android/BaseMainActivity.java | 59 +++- .../timeline/FragmentMastodonTimeline.java | 299 ++++++++++-------- .../ui/pageadapter/FedilabPageAdapter.java | 3 + app/src/main/res/drawable/shape_counter.xml | 9 + .../main/res/layout/notification_badge.xml | 17 + 5 files changed, 247 insertions(+), 140 deletions(-) create mode 100644 app/src/main/res/drawable/shape_counter.xml create mode 100644 app/src/main/res/layout/notification_badge.xml diff --git a/app/src/main/java/app/fedilab/android/BaseMainActivity.java b/app/src/main/java/app/fedilab/android/BaseMainActivity.java index b473c111..be0ca83f 100644 --- a/app/src/main/java/app/fedilab/android/BaseMainActivity.java +++ b/app/src/main/java/app/fedilab/android/BaseMainActivity.java @@ -148,7 +148,7 @@ import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; -public abstract class BaseMainActivity extends BaseActivity implements NetworkStateReceiver.NetworkStateReceiverListener { +public abstract class BaseMainActivity extends BaseActivity implements NetworkStateReceiver.NetworkStateReceiverListener, FragmentMastodonTimeline.UpdateCounters { public static String currentInstance, currentToken, currentUserID, client_id, client_secret, software; public static HashMap> emojis = new HashMap<>(); @@ -1082,6 +1082,63 @@ public abstract class BaseMainActivity extends BaseActivity implements NetworkSt } } + @Override + public void onUpdate(int count, Timeline.TimeLineEnum type, String slug) { + SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(BaseMainActivity.this); + boolean singleBar = sharedpreferences.getBoolean(getString(R.string.SET_USE_SINGLE_TOPBAR), false); + if (!singleBar) { + switch (type) { + case HOME: + if (count > 0) { + binding.bottomNavView.getOrCreateBadge(R.id.nav_home).setNumber(count); + binding.bottomNavView.getBadge(R.id.nav_home).setBackgroundColor(ContextCompat.getColor(BaseMainActivity.this, R.color.cyanea_accent_reference)); + binding.bottomNavView.getBadge(R.id.nav_home).setBadgeTextColor(ThemeHelper.getAttColor(BaseMainActivity.this, R.attr.mTextColor)); + } else { + binding.bottomNavView.removeBadge(R.id.nav_home); + } + break; + case LOCAL: + if (count > 0) { + binding.bottomNavView.getOrCreateBadge(R.id.nav_local).setNumber(count); + binding.bottomNavView.getBadge(R.id.nav_local).setBackgroundColor(ContextCompat.getColor(BaseMainActivity.this, R.color.cyanea_accent_reference)); + binding.bottomNavView.getBadge(R.id.nav_local).setBadgeTextColor(ThemeHelper.getAttColor(BaseMainActivity.this, R.attr.mTextColor)); + } else { + binding.bottomNavView.removeBadge(R.id.nav_local); + } + break; + case PUBLIC: + if (count > 0) { + binding.bottomNavView.getOrCreateBadge(R.id.nav_public).setNumber(count); + binding.bottomNavView.getBadge(R.id.nav_public).setBackgroundColor(ContextCompat.getColor(BaseMainActivity.this, R.color.cyanea_accent_reference)); + binding.bottomNavView.getBadge(R.id.nav_public).setBadgeTextColor(ThemeHelper.getAttColor(BaseMainActivity.this, R.attr.mTextColor)); + } else { + binding.bottomNavView.removeBadge(R.id.nav_public); + } + break; + case NOTIFICATION: + if (count > 0) { + binding.bottomNavView.getOrCreateBadge(R.id.nav_notifications).setNumber(count); + binding.bottomNavView.getBadge(R.id.nav_notifications).setBackgroundColor(ContextCompat.getColor(BaseMainActivity.this, R.color.cyanea_accent_reference)); + binding.bottomNavView.getBadge(R.id.nav_notifications).setBadgeTextColor(ThemeHelper.getAttColor(BaseMainActivity.this, R.attr.mTextColor)); + } else { + binding.bottomNavView.removeBadge(R.id.nav_notifications); + } + break; + case DIRECT: + if (count > 0) { + binding.bottomNavView.getOrCreateBadge(R.id.nav_privates).setNumber(count); + binding.bottomNavView.getBadge(R.id.nav_privates).setBackgroundColor(ContextCompat.getColor(BaseMainActivity.this, R.color.cyanea_accent_reference)); + binding.bottomNavView.getBadge(R.id.nav_privates).setBadgeTextColor(ThemeHelper.getAttColor(BaseMainActivity.this, R.attr.mTextColor)); + } else { + binding.bottomNavView.removeBadge(R.id.nav_privates); + } + break; + } + } + + + } + @Override protected void onDestroy() { LocalBroadcastManager.getInstance(BaseMainActivity.this).unregisterReceiver(broadcast_data); diff --git a/app/src/main/java/app/fedilab/android/ui/fragment/timeline/FragmentMastodonTimeline.java b/app/src/main/java/app/fedilab/android/ui/fragment/timeline/FragmentMastodonTimeline.java index a910b9a6..13321eb3 100644 --- a/app/src/main/java/app/fedilab/android/ui/fragment/timeline/FragmentMastodonTimeline.java +++ b/app/src/main/java/app/fedilab/android/ui/fragment/timeline/FragmentMastodonTimeline.java @@ -77,6 +77,14 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. private StatusAdapter statusAdapter; private Timeline.TimeLineEnum timelineType; private List timelineStatuses; + public UpdateCounters update; + + @Override + public void onResume() { + super.onResume(); + route(DIRECTION.FETCH_NEW, true); + } + //Handle actions that can be done in other fragments private final BroadcastReceiver receive_action = new BroadcastReceiver() { @Override @@ -263,6 +271,81 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. return binding.getRoot(); } + /** + * Update view and pagination when scrolling down + * + * @param fetched_statuses Statuses + */ + private synchronized void dealWithPagination(Statuses fetched_statuses, DIRECTION direction, boolean fetchingMissing, Status statusToUpdate) { + if (binding == null || !isAdded() || getActivity() == null) { + return; + } + binding.swipeContainer.setRefreshing(false); + binding.loadingNextElements.setVisibility(View.GONE); + flagLoading = false; + if (timelineStatuses != null && fetched_statuses != null && fetched_statuses.statuses != null && fetched_statuses.statuses.size() > 0) { + try { + if (statusToUpdate != null) { + new Thread(() -> { + StatusCache statusCache = new StatusCache(); + statusCache.instance = BaseMainActivity.currentInstance; + statusCache.user_id = BaseMainActivity.currentUserID; + statusToUpdate.isFetchMore = false; + statusCache.status = statusToUpdate; + statusCache.status_id = statusToUpdate.id; + try { + new StatusCache(requireActivity()).updateIfExists(statusCache); + } catch (DBException e) { + e.printStackTrace(); + } + }).start(); + } + } catch (Exception ignored) { + } + + flagLoading = fetched_statuses.pagination.max_id == null; + binding.noAction.setVisibility(View.GONE); + if (timelineType == Timeline.TimeLineEnum.ART) { + //We have to split media in different statuses + List mediaStatuses = new ArrayList<>(); + for (Status status : fetched_statuses.statuses) { + if (status.media_attachments.size() > 1) { + for (Attachment attachment : status.media_attachments) { + status.media_attachments = new ArrayList<>(); + status.media_attachments.add(0, attachment); + mediaStatuses.add(status); + } + } + } + fetched_statuses.statuses = mediaStatuses; + } + //Update the timeline with new statuses + int insertedStatus = updateStatusListWith(fetched_statuses.statuses); + + //For these directions, the app will display counters for new messages + if (insertedStatus >= 0 && (direction == DIRECTION.FETCH_NEW || direction == DIRECTION.SCROLL_TOP)) { + update.onUpdate(insertedStatus, timelineType, slug); + } + if (direction == DIRECTION.TOP && fetchingMissing) { + binding.recyclerView.scrollToPosition(getPosition(fetched_statuses.statuses.get(fetched_statuses.statuses.size() - 1)) + 1); + } + if (!fetchingMissing) { + if (fetched_statuses.pagination.max_id == null) { + flagLoading = true; + } else if (max_id == null || fetched_statuses.pagination.max_id.compareTo(max_id) < 0) { + max_id = fetched_statuses.pagination.max_id; + } + if (min_id == null || (fetched_statuses.pagination.min_id != null && fetched_statuses.pagination.min_id.compareTo(min_id) > 0)) { + min_id = fetched_statuses.pagination.min_id; + } + } + } else if (direction == DIRECTION.BOTTOM) { + flagLoading = true; + } + if (direction == DIRECTION.SCROLL_TOP) { + binding.recyclerView.scrollToPosition(0); + } + } /** * Intialize the common view for statuses on different timelines @@ -377,91 +460,13 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. } } - /** - * Update view and pagination when scrolling down - * - * @param fetched_statuses Statuses - */ - private synchronized void dealWithPagination(Statuses fetched_statuses, DIRECTION direction, boolean fetchingMissing, Status statusToUpdate) { - if (binding == null || !isAdded() || getActivity() == null) { - return; - } - binding.swipeContainer.setRefreshing(false); - binding.loadingNextElements.setVisibility(View.GONE); - flagLoading = false; - if (timelineStatuses != null && fetched_statuses != null && fetched_statuses.statuses != null && fetched_statuses.statuses.size() > 0) { - try { - if (statusToUpdate != null) { - new Thread(() -> { - StatusCache statusCache = new StatusCache(); - statusCache.instance = BaseMainActivity.currentInstance; - statusCache.user_id = BaseMainActivity.currentUserID; - statusToUpdate.isFetchMore = false; - statusCache.status = statusToUpdate; - statusCache.status_id = statusToUpdate.id; - try { - new StatusCache(requireActivity()).updateIfExists(statusCache); - } catch (DBException e) { - e.printStackTrace(); - } - }).start(); - } - } catch (Exception ignored) { - } - flagLoading = fetched_statuses.pagination.max_id == null; - binding.noAction.setVisibility(View.GONE); - if (timelineType == Timeline.TimeLineEnum.ART) { - //We have to split media in different statuses - List mediaStatuses = new ArrayList<>(); - for (Status status : fetched_statuses.statuses) { - if (status.media_attachments.size() > 1) { - for (Attachment attachment : status.media_attachments) { - status.media_attachments = new ArrayList<>(); - status.media_attachments.add(0, attachment); - mediaStatuses.add(status); - } - } - } - fetched_statuses.statuses = mediaStatuses; - } - //Update the timeline with new statuses - updateStatusListWith(fetched_statuses.statuses); - if (direction == DIRECTION.TOP && fetchingMissing) { - binding.recyclerView.scrollToPosition(getPosition(fetched_statuses.statuses.get(fetched_statuses.statuses.size() - 1)) + 1); - } - if (!fetchingMissing) { - if (fetched_statuses.pagination.max_id == null) { - flagLoading = true; - } else if (max_id == null || fetched_statuses.pagination.max_id.compareTo(max_id) < 0) { - max_id = fetched_statuses.pagination.max_id; - } - if (min_id == null || (fetched_statuses.pagination.min_id != null && fetched_statuses.pagination.min_id.compareTo(min_id) > 0)) { - min_id = fetched_statuses.pagination.min_id; - } - } - } else if (direction == DIRECTION.BOTTOM) { - flagLoading = true; - } - if (direction == DIRECTION.SCROLL_TOP) { - binding.recyclerView.scrollToPosition(0); - } - } - - /** - * Update view and pagination when scrolling down - * - * @param fetched_statuses Statuses - */ - private synchronized void dealWithPagination(Statuses fetched_statuses, DIRECTION direction, boolean fetchingMissing) { - dealWithPagination(fetched_statuses, direction, fetchingMissing, null); - } - /** * Update the timeline with received statuses * * @param statusListReceived - List Statuses received */ - private void updateStatusListWith(List statusListReceived) { + private int updateStatusListWith(List statusListReceived) { + int insertedStatus = 0; if (statusListReceived != null && statusListReceived.size() > 0) { for (Status statusReceived : statusListReceived) { int position = 0; @@ -477,6 +482,7 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. if (!timelineStatuses.contains(statusReceived) && !statusReceived.pinned && timelineType != Timeline.TimeLineEnum.ACCOUNT_TIMELINE) { timelineStatuses.add(position, statusReceived); statusAdapter.notifyItemInserted(position); + insertedStatus++; } break; } @@ -491,56 +497,16 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. } } } + return insertedStatus; } - @Override - public void onPause() { - storeMarker(); - super.onPause(); - } - - @Override - public void onDestroyView() { - //Update last read id for home timeline - if (isAdded()) { - storeMarker(); - } - LocalBroadcastManager.getInstance(requireActivity()).unregisterReceiver(receive_action); - super.onDestroyView(); - } - - - private void storeMarker() { - if (timelineType == Timeline.TimeLineEnum.HOME && mLayoutManager != null) { - int position = mLayoutManager.findFirstVisibleItemPosition(); - if (timelineStatuses != null && timelineStatuses.size() > position) { - try { - Status status = timelineStatuses.get(position); - SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(requireActivity()); - SharedPreferences.Editor editor = sharedpreferences.edit(); - editor.putString(getString(R.string.SET_INNER_MARKER) + BaseMainActivity.currentUserID + BaseMainActivity.currentInstance + slug, status.id); - editor.apply(); - timelinesVM.addMarker(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, status.id, null); - - } catch (Exception ignored) { - } - } - } - } - - private void router(DIRECTION direction) { - if (networkAvailable == BaseMainActivity.status.UNKNOWN) { - new Thread(() -> { - if (networkAvailable == BaseMainActivity.status.UNKNOWN) { - networkAvailable = Helper.isConnectedToInternet(requireActivity(), BaseMainActivity.currentInstance); - } - Handler mainHandler = new Handler(Looper.getMainLooper()); - Runnable myRunnable = () -> route(direction, false); - mainHandler.post(myRunnable); - }).start(); - } else { - route(direction, false); - } + /** + * Update view and pagination when scrolling down + * + * @param fetched_statuses Statuses + */ + private synchronized void dealWithPagination(Statuses fetched_statuses, DIRECTION direction, boolean fetchingMissing) { + dealWithPagination(fetched_statuses, direction, fetchingMissing, null); } /** @@ -600,7 +566,7 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. } SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(requireActivity()); boolean useCache = sharedpreferences.getBoolean(getString(R.string.SET_USE_CACHE), true); - if (useCache) { + if (useCache && direction != DIRECTION.SCROLL_TOP && direction != DIRECTION.FETCH_NEW) { getCachedStatus(direction, fetchingMissing, timelineParams); } else { getLiveStatus(direction, fetchingMissing, timelineParams, status); @@ -608,6 +574,56 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. } + @Override + public void onPause() { + storeMarker(); + super.onPause(); + } + + @Override + public void onDestroyView() { + //Update last read id for home timeline + if (isAdded()) { + storeMarker(); + } + LocalBroadcastManager.getInstance(requireActivity()).unregisterReceiver(receive_action); + super.onDestroyView(); + } + + + private void storeMarker() { + if (timelineType == Timeline.TimeLineEnum.HOME && mLayoutManager != null) { + int position = mLayoutManager.findFirstVisibleItemPosition(); + if (timelineStatuses != null && timelineStatuses.size() > position) { + try { + Status status = timelineStatuses.get(position); + SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(requireActivity()); + SharedPreferences.Editor editor = sharedpreferences.edit(); + editor.putString(getString(R.string.SET_INNER_MARKER) + BaseMainActivity.currentUserID + BaseMainActivity.currentInstance + slug, status.id); + editor.apply(); + timelinesVM.addMarker(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, status.id, null); + + } catch (Exception ignored) { + } + } + } + } + + private void router(DIRECTION direction) { + if (networkAvailable == BaseMainActivity.status.UNKNOWN) { + new Thread(() -> { + if (networkAvailable == BaseMainActivity.status.UNKNOWN) { + networkAvailable = Helper.isConnectedToInternet(requireActivity(), BaseMainActivity.currentInstance); + } + Handler mainHandler = new Handler(Looper.getMainLooper()); + Runnable myRunnable = () -> route(direction, false); + mainHandler.post(myRunnable); + }).start(); + } else { + route(direction, false); + } + } + private void getCachedStatus(DIRECTION direction, boolean fetchingMissing, TimelinesVM.TimelineParams timelineParams) { if (direction == null) { timelinesVM.getTimelineCache(timelineStatuses, timelineParams) @@ -637,11 +653,11 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. } }); - } else if (direction == DIRECTION.REFRESH || direction == DIRECTION.SCROLL_TOP) { - timelinesVM.getTimeline(timelineStatuses, timelineParams) + } else if (direction == DIRECTION.REFRESH) { + timelinesVM.getTimelineCache(timelineStatuses, timelineParams) .observe(getViewLifecycleOwner(), statusesRefresh -> { if (statusesRefresh == null || statusesRefresh.statuses == null || statusesRefresh.statuses.size() == 0) { - getCachedStatus(direction, fetchingMissing, timelineParams); + getLiveStatus(direction, fetchingMissing, timelineParams, null); } else { if (statusAdapter != null) { dealWithPagination(statusesRefresh, direction, true); @@ -653,7 +669,6 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. } } - private void getLiveStatus(DIRECTION direction, boolean fetchingMissing, TimelinesVM.TimelineParams timelineParams, Status status) { if (direction == null) { timelinesVM.getTimeline(timelineStatuses, timelineParams) @@ -664,7 +679,7 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. } else if (direction == DIRECTION.TOP) { timelinesVM.getTimeline(timelineStatuses, timelineParams) .observe(getViewLifecycleOwner(), statusesTop -> dealWithPagination(statusesTop, DIRECTION.TOP, fetchingMissing, status)); - } else if (direction == DIRECTION.REFRESH || direction == DIRECTION.SCROLL_TOP) { + } else if (direction == DIRECTION.REFRESH || direction == DIRECTION.SCROLL_TOP || direction == DIRECTION.FETCH_NEW) { timelinesVM.getTimeline(timelineStatuses, timelineParams) .observe(getViewLifecycleOwner(), statusesRefresh -> { if (statusAdapter != null) { @@ -677,6 +692,15 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. } + public enum DIRECTION { + TOP, + BOTTOM, + REFRESH, + SCROLL_TOP, + FETCH_NEW + } + + /** * Router for timelines * @@ -887,10 +911,7 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. route(DIRECTION.BOTTOM, true, statusToUpdate); } - public enum DIRECTION { - TOP, - BOTTOM, - REFRESH, - SCROLL_TOP + public interface UpdateCounters { + void onUpdate(int count, Timeline.TimeLineEnum type, String slug); } } \ No newline at end of file diff --git a/app/src/main/java/app/fedilab/android/ui/pageadapter/FedilabPageAdapter.java b/app/src/main/java/app/fedilab/android/ui/pageadapter/FedilabPageAdapter.java index 65b2a4cf..7203263b 100644 --- a/app/src/main/java/app/fedilab/android/ui/pageadapter/FedilabPageAdapter.java +++ b/app/src/main/java/app/fedilab/android/ui/pageadapter/FedilabPageAdapter.java @@ -46,10 +46,12 @@ public class FedilabPageAdapter extends FragmentStatePagerAdapter { private final int toRemove; private final boolean singleBar; private Fragment mCurrentFragment; + private final BaseMainActivity activity; public FedilabPageAdapter(BaseMainActivity activity, FragmentManager fm, Pinned pinned, BottomMenu bottomMenu) { super(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT); this.pinned = pinned; + this.activity = activity; this.bottomMenu = bottomMenu; SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(activity); singleBar = sharedpreferences.getBoolean(activity.getString(R.string.SET_USE_SINGLE_TOPBAR), false); @@ -86,6 +88,7 @@ public class FedilabPageAdapter extends FragmentStatePagerAdapter { @Override public Fragment getItem(int position) { FragmentMastodonTimeline fragment = new FragmentMastodonTimeline(); + fragment.update = activity; Bundle bundle = new Bundle(); //Position 3 is for notifications if (position < (BOTTOM_TIMELINE_COUNT - toRemove) && !singleBar) { diff --git a/app/src/main/res/drawable/shape_counter.xml b/app/src/main/res/drawable/shape_counter.xml new file mode 100644 index 00000000..437e1b69 --- /dev/null +++ b/app/src/main/res/drawable/shape_counter.xml @@ -0,0 +1,9 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/notification_badge.xml b/app/src/main/res/layout/notification_badge.xml new file mode 100644 index 00000000..77fc122c --- /dev/null +++ b/app/src/main/res/layout/notification_badge.xml @@ -0,0 +1,17 @@ + + + + + \ No newline at end of file