Add counter for new messages

This commit is contained in:
Thomas 2022-10-01 17:00:03 +02:00
parent 204cb84dca
commit e2be4871e0
5 changed files with 247 additions and 140 deletions

View file

@ -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<>();
@ -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);

View file

@ -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
@ -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
@ -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;
@ -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);
}
}

View file

@ -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) {

View file

@ -0,0 +1,9 @@
<?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>

View file

@ -0,0 +1,17 @@
<?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>