Loading app/src/main/java/app/fedilab/android/BaseMainActivity.java +58 −1 Original line number Diff line number Diff line Loading @@ -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<String, List<Emoji>> emojis = new HashMap<>(); Loading Loading @@ -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); Loading app/src/main/java/app/fedilab/android/ui/fragment/timeline/FragmentMastodonTimeline.java +160 −139 Original line number Diff line number Diff line Loading @@ -77,6 +77,14 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. private StatusAdapter statusAdapter; private Timeline.TimeLineEnum timelineType; private List<Status> 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 Loading Loading @@ -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<Status> 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 Loading Loading @@ -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<Status> 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<Status> Statuses received */ private void updateStatusListWith(List<Status> statusListReceived) { private int updateStatusListWith(List<Status> statusListReceived) { int insertedStatus = 0; if (statusListReceived != null && statusListReceived.size() > 0) { for (Status statusReceived : statusListReceived) { int position = 0; Loading @@ -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; } Loading @@ -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); } /** Loading Loading @@ -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); Loading @@ -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) Loading Loading @@ -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); Loading @@ -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) Loading @@ -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) { Loading @@ -677,6 +692,15 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. } public enum DIRECTION { TOP, BOTTOM, REFRESH, SCROLL_TOP, FETCH_NEW } /** * Router for timelines * Loading Loading @@ -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 app/src/main/java/app/fedilab/android/ui/pageadapter/FedilabPageAdapter.java +3 −0 Original line number Diff line number Diff line Loading @@ -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); Loading Loading @@ -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) { Loading app/src/main/res/drawable/shape_counter.xml 0 → 100644 +9 −0 Original line number Diff line number Diff line <?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> <solid android:color="@color/cyanea_accent_dark_reference" /> <padding android:left="2dp" android:right="2dp" /> <corners android:radius="3dp" /> </shape> No newline at end of file app/src/main/res/layout/notification_badge.xml 0 → 100644 +17 −0 Original line number Diff line number Diff line <?xml version="1.0" encoding="utf-8"?> <merge xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"> <androidx.appcompat.widget.AppCompatTextView android:id="@+id/notifications.badge" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="top|center_horizontal" android:layout_marginStart="10dp" android:background="@drawable/shape_counter" android:gravity="center" android:padding="3dp" android:textColor="@color/white" android:textSize="11sp" tools:text="9+" /> </merge> No newline at end of file Loading
app/src/main/java/app/fedilab/android/BaseMainActivity.java +58 −1 Original line number Diff line number Diff line Loading @@ -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<String, List<Emoji>> emojis = new HashMap<>(); Loading Loading @@ -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); Loading
app/src/main/java/app/fedilab/android/ui/fragment/timeline/FragmentMastodonTimeline.java +160 −139 Original line number Diff line number Diff line Loading @@ -77,6 +77,14 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. private StatusAdapter statusAdapter; private Timeline.TimeLineEnum timelineType; private List<Status> 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 Loading Loading @@ -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<Status> 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 Loading Loading @@ -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<Status> 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<Status> Statuses received */ private void updateStatusListWith(List<Status> statusListReceived) { private int updateStatusListWith(List<Status> statusListReceived) { int insertedStatus = 0; if (statusListReceived != null && statusListReceived.size() > 0) { for (Status statusReceived : statusListReceived) { int position = 0; Loading @@ -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; } Loading @@ -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); } /** Loading Loading @@ -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); Loading @@ -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) Loading Loading @@ -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); Loading @@ -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) Loading @@ -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) { Loading @@ -677,6 +692,15 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. } public enum DIRECTION { TOP, BOTTOM, REFRESH, SCROLL_TOP, FETCH_NEW } /** * Router for timelines * Loading Loading @@ -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
app/src/main/java/app/fedilab/android/ui/pageadapter/FedilabPageAdapter.java +3 −0 Original line number Diff line number Diff line Loading @@ -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); Loading Loading @@ -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) { Loading
app/src/main/res/drawable/shape_counter.xml 0 → 100644 +9 −0 Original line number Diff line number Diff line <?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> <solid android:color="@color/cyanea_accent_dark_reference" /> <padding android:left="2dp" android:right="2dp" /> <corners android:radius="3dp" /> </shape> No newline at end of file
app/src/main/res/layout/notification_badge.xml 0 → 100644 +17 −0 Original line number Diff line number Diff line <?xml version="1.0" encoding="utf-8"?> <merge xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"> <androidx.appcompat.widget.AppCompatTextView android:id="@+id/notifications.badge" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="top|center_horizontal" android:layout_marginStart="10dp" android:background="@drawable/shape_counter" android:gravity="center" android:padding="3dp" android:textColor="@color/white" android:textSize="11sp" tools:text="9+" /> </merge> No newline at end of file