Apply logic

This commit is contained in:
Thomas 2022-06-19 19:24:42 +02:00
parent 5d2f7c3475
commit e1677b82d9
5 changed files with 415 additions and 32 deletions

View file

@ -14,7 +14,9 @@ package app.fedilab.android.client.entities.api;
* You should have received a copy of the GNU General Public License along with Fedilab; if not, * You should have received a copy of the GNU General Public License along with Fedilab; if not,
* see <http://www.gnu.org/licenses>. */ * see <http://www.gnu.org/licenses>. */
import com.google.gson.Gson;
import com.google.gson.annotations.SerializedName; import com.google.gson.annotations.SerializedName;
import com.google.gson.reflect.TypeToken;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
@ -33,4 +35,38 @@ public class Notification {
public Status status; public Status status;
public transient List<Notification> relatedNotifications; public transient List<Notification> relatedNotifications;
public boolean isFetchMore;
public boolean isFetchMoreHidden = false;
/**
* Serialized a list of Notification class
*
* @param notifications List of {@link Notification} to serialize
* @return String serialized emoji list
*/
public static String notificationsToStringStorage(List<Notification> notifications) {
Gson gson = new Gson();
try {
return gson.toJson(notifications);
} catch (Exception e) {
return null;
}
}
/**
* Unserialized a notification List
*
* @param serializedNotificationList String serialized Status list
* @return List of {@link Notification}
*/
public static List<Notification> restoreNotificationsFromString(String serializedNotificationList) {
Gson gson = new Gson();
try {
return gson.fromJson(serializedNotificationList, new TypeToken<List<Notification>>() {
}.getType());
} catch (Exception e) {
return null;
}
}
} }

View file

@ -24,11 +24,13 @@ import com.google.gson.annotations.SerializedName;
import java.util.List; import java.util.List;
import app.fedilab.android.client.entities.api.Notification;
import app.fedilab.android.client.entities.api.Status; import app.fedilab.android.client.entities.api.Status;
import app.fedilab.android.exception.DBException; import app.fedilab.android.exception.DBException;
import app.fedilab.android.helper.MastodonHelper; import app.fedilab.android.helper.MastodonHelper;
import app.fedilab.android.helper.SpannableHelper; import app.fedilab.android.helper.SpannableHelper;
import app.fedilab.android.sqlite.Sqlite; import app.fedilab.android.sqlite.Sqlite;
import app.fedilab.android.ui.fragment.timeline.FragmentMastodonNotification;
public class QuickLoad { public class QuickLoad {
@ -45,6 +47,8 @@ public class QuickLoad {
public int position; public int position;
@SerializedName("statuses") @SerializedName("statuses")
public List<Status> statuses; public List<Status> statuses;
@SerializedName("notifications")
public List<Notification> notifications;
private Context _mContext; private Context _mContext;
public QuickLoad() { public QuickLoad() {
@ -134,6 +138,29 @@ public class QuickLoad {
} }
} }
/**
* @param position - current position in timeline
* @param notificationTypeEnum - Timeline.NotificationTypeEnum
* @param notificationList - List<Notification> to save
*/
public void storeNotifications(int position, String user_id, String instance, FragmentMastodonNotification.NotificationTypeEnum notificationTypeEnum, List<Notification> notificationList) {
String key = notificationTypeEnum.getValue();
QuickLoad quickLoad = new QuickLoad();
quickLoad.position = position;
quickLoad.notifications = notificationList;
quickLoad.slug = key;
quickLoad.instance = instance;
quickLoad.user_id = user_id;
purge(quickLoad);
try {
insertOrUpdate(quickLoad);
} catch (DBException e) {
e.printStackTrace();
}
}
/** /**
* Insert a QuickLoad in db * Insert a QuickLoad in db
* *
@ -149,7 +176,11 @@ public class QuickLoad {
values.put(Sqlite.COL_INSTANCE, quickLoad.instance); values.put(Sqlite.COL_INSTANCE, quickLoad.instance);
values.put(Sqlite.COL_SLUG, quickLoad.slug); values.put(Sqlite.COL_SLUG, quickLoad.slug);
values.put(Sqlite.COL_POSITION, quickLoad.position); values.put(Sqlite.COL_POSITION, quickLoad.position);
values.put(Sqlite.COL_STATUSES, StatusDraft.mastodonStatusListToStringStorage(quickLoad.statuses)); if (quickLoad.statuses != null) {
values.put(Sqlite.COL_STATUSES, StatusDraft.mastodonStatusListToStringStorage(quickLoad.statuses));
} else if (quickLoad.notifications != null) {
values.put(Sqlite.COL_STATUSES, Notification.notificationsToStringStorage(quickLoad.notifications));
}
//Inserts token //Inserts token
try { try {
db.insertOrThrow(Sqlite.TABLE_QUICK_LOAD, null, values); db.insertOrThrow(Sqlite.TABLE_QUICK_LOAD, null, values);
@ -370,6 +401,40 @@ public class QuickLoad {
return null; return null;
} }
/**
* Retrieves saved values
*
* @param notificationTypeEnum - FragmentMastodonNotification.NotificationTypeEnum
* @return SavedValues
*/
public QuickLoad getSavedValue(String user_id, String instance, FragmentMastodonNotification.NotificationTypeEnum notificationTypeEnum) {
String key = notificationTypeEnum.getValue();
try {
return get(user_id, instance, key);
} catch (DBException e) {
e.printStackTrace();
}
return null;
}
/**
* Retrieves saved values
*
* @param notificationTypeEnum - FragmentMastodonNotification.NotificationTypeEnum
* @return SavedValues
*/
public QuickLoad getSavedValue(BaseAccount account, FragmentMastodonNotification.NotificationTypeEnum notificationTypeEnum) {
String key = notificationTypeEnum.getValue();
try {
return get(key, account);
} catch (DBException e) {
e.printStackTrace();
}
return null;
}
/** /**
* Retrieves saved values * Retrieves saved values
* *

View file

@ -38,6 +38,7 @@ import app.fedilab.android.R;
import app.fedilab.android.activities.ProfileActivity; import app.fedilab.android.activities.ProfileActivity;
import app.fedilab.android.client.entities.api.Notification; import app.fedilab.android.client.entities.api.Notification;
import app.fedilab.android.client.entities.app.Timeline; import app.fedilab.android.client.entities.app.Timeline;
import app.fedilab.android.databinding.DrawerFetchMoreBinding;
import app.fedilab.android.databinding.DrawerFollowBinding; import app.fedilab.android.databinding.DrawerFollowBinding;
import app.fedilab.android.databinding.DrawerStatusNotificationBinding; import app.fedilab.android.databinding.DrawerStatusNotificationBinding;
import app.fedilab.android.databinding.NotificationsRelatedAccountsBinding; import app.fedilab.android.databinding.NotificationsRelatedAccountsBinding;
@ -56,7 +57,9 @@ public class NotificationAdapter extends RecyclerView.Adapter<RecyclerView.ViewH
private final int TYPE_FAVOURITE = 4; private final int TYPE_FAVOURITE = 4;
private final int TYPE_POLL = 5; private final int TYPE_POLL = 5;
private final int TYPE_STATUS = 6; private final int TYPE_STATUS = 6;
private final int NOTIFICATION_FETCH_MORE = 7;
private Context context; private Context context;
public FetchMoreCallBack fetchMoreCallBack;
public NotificationAdapter(List<Notification> notificationList) { public NotificationAdapter(List<Notification> notificationList) {
this.notificationList = notificationList; this.notificationList = notificationList;
@ -72,6 +75,9 @@ public class NotificationAdapter extends RecyclerView.Adapter<RecyclerView.ViewH
@Override @Override
public int getItemViewType(int position) { public int getItemViewType(int position) {
if (notificationList.get(position).isFetchMore) {
return NOTIFICATION_FETCH_MORE;
}
String type = notificationList.get(position).type; String type = notificationList.get(position).type;
switch (type) { switch (type) {
case "follow": case "follow":
@ -99,6 +105,9 @@ public class NotificationAdapter extends RecyclerView.Adapter<RecyclerView.ViewH
if (viewType == TYPE_FOLLOW || viewType == TYPE_FOLLOW_REQUEST) { if (viewType == TYPE_FOLLOW || viewType == TYPE_FOLLOW_REQUEST) {
DrawerFollowBinding itemBinding = DrawerFollowBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false); DrawerFollowBinding itemBinding = DrawerFollowBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
return new ViewHolderFollow(itemBinding); return new ViewHolderFollow(itemBinding);
} else if (viewType == NOTIFICATION_FETCH_MORE) { //Fetch more button
DrawerFetchMoreBinding itemBinding = DrawerFetchMoreBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
return new StatusAdapter.StatusViewHolder(itemBinding);
} else { } else {
DrawerStatusNotificationBinding itemBinding = DrawerStatusNotificationBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false); DrawerStatusNotificationBinding itemBinding = DrawerStatusNotificationBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
return new StatusAdapter.StatusViewHolder(itemBinding); return new StatusAdapter.StatusViewHolder(itemBinding);
@ -132,6 +141,17 @@ public class NotificationAdapter extends RecyclerView.Adapter<RecyclerView.ViewH
// start the new activity // start the new activity
context.startActivity(intent, options.toBundle()); context.startActivity(intent, options.toBundle());
}); });
} else if (viewHolder.getItemViewType() == NOTIFICATION_FETCH_MORE) {
StatusAdapter.StatusViewHolder holder = (StatusAdapter.StatusViewHolder) viewHolder;
holder.bindingFetchMore.fetchMore.setEnabled(!notification.isFetchMoreHidden);
holder.bindingFetchMore.fetchMore.setOnClickListener(v -> {
if (position + 1 < notificationList.size()) {
//We hide the button
notification.isFetchMoreHidden = true;
notifyItemChanged(position);
fetchMoreCallBack.onClick(notificationList.get(position + 1).id, notification.id);
}
});
} else { } else {
StatusAdapter.StatusViewHolder holderStatus = (StatusAdapter.StatusViewHolder) viewHolder; StatusAdapter.StatusViewHolder holderStatus = (StatusAdapter.StatusViewHolder) viewHolder;
holderStatus.bindingNotification.status.typeOfNotification.setVisibility(View.VISIBLE); holderStatus.bindingNotification.status.typeOfNotification.setVisibility(View.VISIBLE);
@ -234,6 +254,10 @@ public class NotificationAdapter extends RecyclerView.Adapter<RecyclerView.ViewH
} }
} }
public interface FetchMoreCallBack {
void onClick(String min_id, String fetchmoreId);
}
public long getItemId(int position) { public long getItemId(int position) {
return position; return position;

View file

@ -14,6 +14,8 @@ package app.fedilab.android.ui.fragment.timeline;
* You should have received a copy of the GNU General Public License along with Fedilab; if not, * You should have received a copy of the GNU General Public License along with Fedilab; if not,
* see <http://www.gnu.org/licenses>. */ * see <http://www.gnu.org/licenses>. */
import static app.fedilab.android.BaseMainActivity.networkAvailable;
import android.app.NotificationManager; import android.app.NotificationManager;
import android.content.BroadcastReceiver; import android.content.BroadcastReceiver;
import android.content.Context; import android.content.Context;
@ -21,6 +23,8 @@ import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@ -41,9 +45,13 @@ import java.util.List;
import app.fedilab.android.BaseMainActivity; import app.fedilab.android.BaseMainActivity;
import app.fedilab.android.R; import app.fedilab.android.R;
import app.fedilab.android.activities.MainActivity;
import app.fedilab.android.client.entities.api.Notification; import app.fedilab.android.client.entities.api.Notification;
import app.fedilab.android.client.entities.api.Notifications; import app.fedilab.android.client.entities.api.Notifications;
import app.fedilab.android.client.entities.api.Pagination;
import app.fedilab.android.client.entities.api.Status; import app.fedilab.android.client.entities.api.Status;
import app.fedilab.android.client.entities.api.Statuses;
import app.fedilab.android.client.entities.app.QuickLoad;
import app.fedilab.android.databinding.FragmentPaginationBinding; import app.fedilab.android.databinding.FragmentPaginationBinding;
import app.fedilab.android.helper.Helper; import app.fedilab.android.helper.Helper;
import app.fedilab.android.helper.MastodonHelper; import app.fedilab.android.helper.MastodonHelper;
@ -52,15 +60,16 @@ import app.fedilab.android.ui.drawer.NotificationAdapter;
import app.fedilab.android.viewmodel.mastodon.NotificationsVM; import app.fedilab.android.viewmodel.mastodon.NotificationsVM;
public class FragmentMastodonNotification extends Fragment { public class FragmentMastodonNotification extends Fragment implements NotificationAdapter.FetchMoreCallBack {
private FragmentPaginationBinding binding; private FragmentPaginationBinding binding;
private NotificationsVM notificationsVM; private NotificationsVM notificationsVM;
private FragmentMastodonNotification currentFragment; private FragmentMastodonNotification currentFragment;
private boolean flagLoading; private boolean flagLoading;
private List<Notification> notifications; private static final int NOTIFICATION_PRESENT = -1;
private String max_id; private static final int NOTIFICATION__AT_THE_BOTTOM = -2;
private List<Notification> notificationList;
private NotificationAdapter notificationAdapter; private NotificationAdapter notificationAdapter;
private final BroadcastReceiver receive_action = new BroadcastReceiver() { private final BroadcastReceiver receive_action = new BroadcastReceiver() {
@Override @Override
@ -71,10 +80,10 @@ public class FragmentMastodonNotification extends Fragment {
if (receivedStatus != null && notificationAdapter != null) { if (receivedStatus != null && notificationAdapter != null) {
int position = getPosition(receivedStatus); int position = getPosition(receivedStatus);
if (position >= 0) { if (position >= 0) {
if (notifications.get(position).status != null) { if (notificationList.get(position).status != null) {
notifications.get(position).status.reblog = receivedStatus.reblog; notificationList.get(position).status.reblog = receivedStatus.reblog;
notifications.get(position).status.favourited = receivedStatus.favourited; notificationList.get(position).status.favourited = receivedStatus.favourited;
notifications.get(position).status.bookmarked = receivedStatus.bookmarked; notificationList.get(position).status.bookmarked = receivedStatus.bookmarked;
notificationAdapter.notifyItemChanged(position); notificationAdapter.notifyItemChanged(position);
} }
} }
@ -82,6 +91,11 @@ public class FragmentMastodonNotification extends Fragment {
} }
} }
}; };
private Notifications notifications;
private String max_id, min_id, min_id_fetch_more;
private LinearLayoutManager mLayoutManager;
private String instance, user_id;
private ArrayList<String> idOfAddedNotifications;
private NotificationTypeEnum notificationType; private NotificationTypeEnum notificationType;
private List<String> excludeType; private List<String> excludeType;
private boolean aggregateNotification; private boolean aggregateNotification;
@ -95,7 +109,7 @@ public class FragmentMastodonNotification extends Fragment {
private int getPosition(Status status) { private int getPosition(Status status) {
int position = 0; int position = 0;
boolean found = false; boolean found = false;
for (Notification _notification : notifications) { for (Notification _notification : notificationList) {
if (_notification.status != null && _notification.status.id.compareTo(status.id) == 0) { if (_notification.status != null && _notification.status.id.compareTo(status.id) == 0) {
found = true; found = true;
break; break;
@ -110,6 +124,9 @@ public class FragmentMastodonNotification extends Fragment {
currentFragment = this; currentFragment = this;
flagLoading = false; flagLoading = false;
instance = MainActivity.currentInstance;
user_id = MainActivity.currentUserID;
idOfAddedNotifications = new ArrayList<>();
binding = FragmentPaginationBinding.inflate(inflater, container, false); binding = FragmentPaginationBinding.inflate(inflater, container, false);
View root = binding.getRoot(); View root = binding.getRoot();
if (getArguments() != null) { if (getArguments() != null) {
@ -160,12 +177,30 @@ public class FragmentMastodonNotification extends Fragment {
excludeType.remove("follow"); excludeType.remove("follow");
excludeType.remove("follow_request"); excludeType.remove("follow_request");
} }
notificationsVM.getNotifications(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, null, null, null, MastodonHelper.statusesPerCall(requireActivity()), excludeType, null) new Thread(() -> {
.observe(getViewLifecycleOwner(), this::initializeNotificationView); QuickLoad quickLoad = new QuickLoad(requireActivity()).getSavedValue(MainActivity.currentUserID, MainActivity.currentInstance, notificationType);
}).start();
router(null);
LocalBroadcastManager.getInstance(requireActivity()).registerReceiver(receive_action, new IntentFilter(Helper.RECEIVE_STATUS_ACTION)); LocalBroadcastManager.getInstance(requireActivity()).registerReceiver(receive_action, new IntentFilter(Helper.RECEIVE_STATUS_ACTION));
return root; return root;
} }
private void router(FragmentMastodonTimeline.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);
}
}
@Override @Override
public void onResume() { public void onResume() {
super.onResume(); super.onResume();
@ -173,6 +208,7 @@ public class FragmentMastodonNotification extends Fragment {
notificationManager.cancel(Helper.NOTIFICATION_USER_NOTIF); notificationManager.cancel(Helper.NOTIFICATION_USER_NOTIF);
} }
/** /**
* Intialize the view for notifications * Intialize the view for notifications
* *
@ -197,20 +233,26 @@ public class FragmentMastodonNotification extends Fragment {
notifications.notifications = aggregateNotifications(notifications.notifications); notifications.notifications = aggregateNotifications(notifications.notifications);
} }
if (notificationAdapter != null && this.notifications != null) { if (notificationAdapter != null && this.notifications != null) {
int size = this.notifications.size(); int size = this.notificationList.size();
this.notifications.clear(); this.notificationList.clear();
this.notifications = new ArrayList<>(); this.notificationList = new ArrayList<>();
notificationAdapter.notifyItemRangeRemoved(0, size); notificationAdapter.notifyItemRangeRemoved(0, size);
} }
this.notifications = notifications.notifications; this.notificationList = notifications.notifications;
notificationAdapter = new NotificationAdapter(this.notifications); notificationAdapter = new NotificationAdapter(this.notificationList);
LinearLayoutManager mLayoutManager = new LinearLayoutManager(requireActivity()); mLayoutManager = new LinearLayoutManager(requireActivity());
binding.recyclerView.setLayoutManager(mLayoutManager); binding.recyclerView.setLayoutManager(mLayoutManager);
binding.recyclerView.setAdapter(notificationAdapter); binding.recyclerView.setAdapter(notificationAdapter);
max_id = notifications.pagination.max_id; max_id = notifications.pagination.max_id;
binding.recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { binding.recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override @Override
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
if (requireActivity() instanceof BaseMainActivity) {
if (dy < 0 && !((BaseMainActivity) requireActivity()).getFloatingVisibility())
((BaseMainActivity) requireActivity()).manageFloatingButton(true);
if (dy > 0 && ((BaseMainActivity) requireActivity()).getFloatingVisibility())
((BaseMainActivity) requireActivity()).manageFloatingButton(false);
}
int firstVisibleItem = mLayoutManager.findFirstVisibleItemPosition(); int firstVisibleItem = mLayoutManager.findFirstVisibleItemPosition();
if (dy > 0) { if (dy > 0) {
int visibleItemCount = mLayoutManager.getChildCount(); int visibleItemCount = mLayoutManager.getChildCount();
@ -219,28 +261,83 @@ public class FragmentMastodonNotification extends Fragment {
if (!flagLoading) { if (!flagLoading) {
flagLoading = true; flagLoading = true;
binding.loadingNextElements.setVisibility(View.VISIBLE); binding.loadingNextElements.setVisibility(View.VISIBLE);
notificationsVM.getNotifications(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, max_id, null, null, MastodonHelper.statusesPerCall(requireActivity()), excludeType, null) router(FragmentMastodonTimeline.DIRECTION.BOTTOM);
.observe(FragmentMastodonNotification.this, fetched_notifications -> dealWithPagination(fetched_notifications));
} }
} else { } else {
binding.loadingNextElements.setVisibility(View.GONE); binding.loadingNextElements.setVisibility(View.GONE);
} }
} else if (firstVisibleItem == 0) { //Scroll top and item is zero
if (!flagLoading) {
flagLoading = true;
binding.loadingNextElements.setVisibility(View.VISIBLE);
router(FragmentMastodonTimeline.DIRECTION.TOP);
}
} }
}
});
}
});
binding.swipeContainer.setOnRefreshListener(() -> { binding.swipeContainer.setOnRefreshListener(() -> {
if (this.notifications.size() > 0) { binding.swipeContainer.setRefreshing(true);
binding.swipeContainer.setRefreshing(true); flagLoading = false;
max_id = null; route(FragmentMastodonTimeline.DIRECTION.REFRESH, true);
flagLoading = false;
notificationsVM.getNotifications(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, null, null, null, MastodonHelper.statusesPerCall(requireActivity()), excludeType, null)
.observe(FragmentMastodonNotification.this, this::initializeNotificationView);
}
}); });
} }
/**
* Router for timelines
*
* @param direction - DIRECTION null if first call, then is set to TOP or BOTTOM depending of scroll
*/
private void route(FragmentMastodonTimeline.DIRECTION direction, boolean fetchingMissing) {
new Thread(() -> {
if (binding == null) {
return;
}
QuickLoad quickLoad = new QuickLoad(requireActivity()).getSavedValue(MainActivity.currentUserID, MainActivity.currentInstance, notificationType);
if (direction != FragmentMastodonTimeline.DIRECTION.REFRESH && !fetchingMissing && !binding.swipeContainer.isRefreshing() && direction == null && quickLoad != null && quickLoad.statuses != null && quickLoad.statuses.size() > 0) {
Statuses statuses = new Statuses();
statuses.statuses = quickLoad.statuses;
statuses.pagination = new Pagination();
statuses.pagination.max_id = quickLoad.statuses.get(quickLoad.statuses.size() - 1).id;
statuses.pagination.min_id = quickLoad.statuses.get(0).id;
Handler mainHandler = new Handler(Looper.getMainLooper());
Runnable myRunnable = () -> initializeNotificationView(notifications);
mainHandler.post(myRunnable);
} else {
Handler mainHandler = new Handler(Looper.getMainLooper());
Runnable myRunnable = () -> {
if (!isAdded()) {
return;
}
if (direction == null) {
notificationsVM.getNotifications(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, null, null, null, MastodonHelper.statusesPerCall(requireActivity()), excludeType, null)
.observe(getViewLifecycleOwner(), this::initializeNotificationView);
} else if (direction == FragmentMastodonTimeline.DIRECTION.BOTTOM) {
notificationsVM.getNotifications(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, max_id, null, null, MastodonHelper.statusesPerCall(requireActivity()), excludeType, null)
.observe(getViewLifecycleOwner(), this::initializeNotificationView);
} else if (direction == FragmentMastodonTimeline.DIRECTION.TOP) {
notificationsVM.getNotifications(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, null, null, fetchingMissing ? min_id_fetch_more : min_id, MastodonHelper.statusesPerCall(requireActivity()), excludeType, null)
.observe(getViewLifecycleOwner(), this::initializeNotificationView);
} else if (direction == FragmentMastodonTimeline.DIRECTION.REFRESH) {
notificationsVM.getNotifications(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, null, null, null, MastodonHelper.statusesPerCall(requireActivity()), excludeType, null)
.observe(getViewLifecycleOwner(), notificationsReceived -> {
if (notificationAdapter != null) {
dealWithPagination(notificationsReceived);
} else {
initializeNotificationView(notificationsReceived);
}
});
}
};
mainHandler.post(myRunnable);
}
}).start();
}
private List<Notification> aggregateNotifications(List<Notification> notifications) { private List<Notification> aggregateNotifications(List<Notification> notifications) {
List<Notification> notificationList = new ArrayList<>(); List<Notification> notificationList = new ArrayList<>();
int refPosition = 0; int refPosition = 0;
@ -271,6 +368,124 @@ public class FragmentMastodonNotification extends Fragment {
binding.recyclerView.scrollToPosition(0); binding.recyclerView.scrollToPosition(0);
} }
/**
* Update view and pagination when scrolling down
*
* @param fetched_notifications Notifications
*/
private synchronized void dealWithPagination(Notifications fetched_notifications, FragmentMastodonTimeline.DIRECTION direction, boolean fetchingMissing) {
if (binding == null) {
return;
}
int currentPosition = mLayoutManager.findFirstVisibleItemPosition();
binding.swipeContainer.setRefreshing(false);
binding.loadingNextElements.setVisibility(View.GONE);
flagLoading = false;
if (notificationList != null && fetched_notifications != null && fetched_notifications.notifications != null && fetched_notifications.notifications.size() > 0) {
flagLoading = fetched_notifications.pagination.max_id == null;
binding.noAction.setVisibility(View.GONE);
//Update the timeline with new statuses
int inserted = updateNotificationListWith(direction, fetched_notifications.notifications, fetchingMissing);
if (fetchingMissing) {
// binding.recyclerView.scrollToPosition(currentPosition + inserted);
}
if (!fetchingMissing) {
if (fetched_notifications.pagination.max_id == null) {
flagLoading = true;
} else if (max_id == null || fetched_notifications.pagination.max_id.compareTo(max_id) < 0) {
max_id = fetched_notifications.pagination.max_id;
}
if (min_id == null || (fetched_notifications.pagination.min_id != null && fetched_notifications.pagination.min_id.compareTo(min_id) > 0)) {
min_id = fetched_notifications.pagination.min_id;
}
}
} else if (direction == FragmentMastodonTimeline.DIRECTION.BOTTOM) {
flagLoading = true;
}
}
/**
* Update the timeline with received statuses
*
* @param notificationsReceived - List<Notification> Notifications received
* @param fetchingMissing - boolean if the call concerns fetching messages (ie: refresh of from fetch more button)
* @return int - Number of messages that have been inserted in the middle of the timeline (ie between other statuses)
*/
private int updateNotificationListWith(FragmentMastodonTimeline.DIRECTION direction, List<Notification> notificationsReceived, boolean fetchingMissing) {
int numberInserted = 0;
int lastInsertedPosition = 0;
int initialInsertedPosition = NOTIFICATION_PRESENT;
if (notificationsReceived != null && notificationsReceived.size() > 0) {
int insertedPosition = NOTIFICATION_PRESENT;
for (Notification notificationReceived : notificationsReceived) {
insertedPosition = insertNotification(notificationReceived);
if (insertedPosition != NOTIFICATION_PRESENT && insertedPosition != NOTIFICATION__AT_THE_BOTTOM) {
numberInserted++;
if (initialInsertedPosition == NOTIFICATION_PRESENT) {
initialInsertedPosition = insertedPosition;
}
if (insertedPosition < initialInsertedPosition) {
initialInsertedPosition = lastInsertedPosition;
}
}
}
lastInsertedPosition = initialInsertedPosition + numberInserted;
//lastInsertedPosition contains the position of the last inserted status
//If there were no overlap for top status
if (fetchingMissing && insertedPosition != NOTIFICATION_PRESENT && insertedPosition != NOTIFICATION__AT_THE_BOTTOM && this.notificationList.size() > insertedPosition) {
Notification notificationFetchMore = new Notification();
notificationFetchMore.isFetchMore = true;
notificationFetchMore.id = Helper.generateString();
int insertAt;
if (direction == FragmentMastodonTimeline.DIRECTION.REFRESH) {
insertAt = lastInsertedPosition;
} else {
insertAt = initialInsertedPosition;
}
this.notificationList.add(insertAt, notificationFetchMore);
notificationAdapter.notifyItemInserted(insertAt);
}
}
return numberInserted;
}
/**
* Insert a status if not yet in the timeline
*
* @param notificationReceived - Notification coming from the api/db
* @return int >= 0 | STATUS_PRESENT = -1 | STATUS_AT_THE_BOTTOM = -2
*/
private int insertNotification(Notification notificationReceived) {
if (idOfAddedNotifications.contains(notificationReceived.id)) {
return NOTIFICATION_PRESENT;
}
int position = 0;
//We loop through messages already in the timeline
for (Notification notificationsAlreadyPresent : this.notificationList) {
//We compare the date of each status and we only add status having a date greater than the another, it is inserted at this position
//Pinned messages are ignored because their date can be older
if (notificationReceived.id.compareTo(notificationsAlreadyPresent.id) > 0) {
//We add the status to a list of id - thus we know it is already in the timeline
idOfAddedNotifications.add(notificationReceived.id);
this.notificationList.add(position, notificationReceived);
notificationAdapter.notifyItemInserted(position);
break;
}
position++;
}
//Statuses added at the bottom, we flag them by position = -2 for not dealing with them and fetch more
if (position == this.notificationList.size()) {
//We add the status to a list of id - thus we know it is already in the timeline
idOfAddedNotifications.add(notificationReceived.id);
this.notificationList.add(position, notificationReceived);
notificationAdapter.notifyItemInserted(position);
return NOTIFICATION__AT_THE_BOTTOM;
}
return position;
}
/** /**
* Update view and pagination when scrolling down * Update view and pagination when scrolling down
* *
@ -286,10 +501,10 @@ public class FragmentMastodonNotification extends Fragment {
} }
int startId = 0; int startId = 0;
//There are some statuses present in the timeline //There are some statuses present in the timeline
if (currentFragment.notifications.size() > 0) { if (currentFragment.notificationList.size() > 0) {
startId = currentFragment.notifications.size(); startId = currentFragment.notificationList.size();
} }
currentFragment.notifications.addAll(fetched_notifications.notifications); currentFragment.notificationList.addAll(fetched_notifications.notifications);
max_id = fetched_notifications.pagination.max_id; max_id = fetched_notifications.pagination.max_id;
notificationAdapter.notifyItemRangeInserted(startId, fetched_notifications.notifications.size()); notificationAdapter.notifyItemRangeInserted(startId, fetched_notifications.notifications.size());
} else { } else {
@ -299,6 +514,15 @@ public class FragmentMastodonNotification extends Fragment {
@Override @Override
public void onDestroyView() { public void onDestroyView() {
if (mLayoutManager != null) {
int position = mLayoutManager.findFirstVisibleItemPosition();
new Thread(() -> {
try {
new QuickLoad(requireActivity()).storeNotifications(position, user_id, instance, notificationType, notificationList);
} catch (Exception ignored) {
}
}).start();
}
super.onDestroyView(); super.onDestroyView();
binding.recyclerView.setAdapter(null); binding.recyclerView.setAdapter(null);
LocalBroadcastManager.getInstance(requireActivity()).unregisterReceiver(receive_action); LocalBroadcastManager.getInstance(requireActivity()).unregisterReceiver(receive_action);
@ -306,6 +530,20 @@ public class FragmentMastodonNotification extends Fragment {
binding = null; binding = null;
} }
@Override
public void onPause() {
if (mLayoutManager != null) {
int position = mLayoutManager.findFirstVisibleItemPosition();
new Thread(() -> {
try {
new QuickLoad(requireActivity()).storeNotifications(position, user_id, instance, notificationType, notificationList);
} catch (Exception ignored) {
}
}).start();
}
super.onPause();
}
public enum NotificationTypeEnum { public enum NotificationTypeEnum {
@SerializedName("ALL") @SerializedName("ALL")
@ -334,4 +572,24 @@ public class FragmentMastodonNotification extends Fragment {
} }
} }
@Override
public void onClick(String min_id, String id) {
//Fetch more has been pressed
min_id_fetch_more = min_id;
Notification notification = null;
int position = 0;
for (Notification currentNotification : this.notificationList) {
if (currentNotification.id.compareTo(id) == 0) {
notification = currentNotification;
break;
}
position++;
}
if (notification != null) {
this.notificationList.remove(position);
notificationAdapter.notifyItemRemoved(position);
}
route(FragmentMastodonTimeline.DIRECTION.TOP, true);
}
} }

View file

@ -566,10 +566,10 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter.
*/ */
private void route(DIRECTION direction, boolean fetchingMissing) { private void route(DIRECTION direction, boolean fetchingMissing) {
new Thread(() -> { new Thread(() -> {
QuickLoad quickLoad = new QuickLoad(requireActivity()).getSavedValue(MainActivity.currentUserID, MainActivity.currentInstance, timelineType, ident);
if (binding == null) { if (binding == null) {
return; return;
} }
QuickLoad quickLoad = new QuickLoad(requireActivity()).getSavedValue(MainActivity.currentUserID, MainActivity.currentInstance, timelineType, ident);
if (direction != DIRECTION.REFRESH && !fetchingMissing && !binding.swipeContainer.isRefreshing() && direction == null && quickLoad != null && quickLoad.statuses != null && quickLoad.statuses.size() > 0) { if (direction != DIRECTION.REFRESH && !fetchingMissing && !binding.swipeContainer.isRefreshing() && direction == null && quickLoad != null && quickLoad.statuses != null && quickLoad.statuses.size() > 0) {
Statuses statuses = new Statuses(); Statuses statuses = new Statuses();
statuses.statuses = quickLoad.statuses; statuses.statuses = quickLoad.statuses;