mirror of
				https://codeberg.org/tom79/Fedilab.git
				synced 2025-10-20 11:20:16 +03:00 
			
		
		
		
	Implement cache for conversations
This commit is contained in:
		
							parent
							
								
									c4dbe80a05
								
							
						
					
					
						commit
						204cb84dca
					
				
					 11 changed files with 607 additions and 63 deletions
				
			
		|  | @ -1,5 +1,7 @@ | ||||||
| package app.fedilab.android.client.entities.api; | package app.fedilab.android.client.entities.api; | ||||||
| 
 | 
 | ||||||
|  | import androidx.annotation.Nullable; | ||||||
|  | 
 | ||||||
| import com.google.gson.annotations.SerializedName; | import com.google.gson.annotations.SerializedName; | ||||||
| 
 | 
 | ||||||
| import java.util.List; | import java.util.List; | ||||||
|  | @ -27,4 +29,25 @@ public class Conversation { | ||||||
|     public List<Account> accounts; |     public List<Account> accounts; | ||||||
|     @SerializedName("last_status") |     @SerializedName("last_status") | ||||||
|     public Status last_status; |     public Status last_status; | ||||||
|  |     public boolean isFetchMore = false; | ||||||
|  |     @SerializedName("cached") | ||||||
|  |     public boolean cached = false; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     public PositionFetchMore positionFetchMore = PositionFetchMore.BOTTOM; | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public boolean equals(@Nullable Object obj) { | ||||||
|  |         boolean same = false; | ||||||
|  |         if (obj instanceof Conversation) { | ||||||
|  |             same = this.id.equals(((Conversation) obj).id); | ||||||
|  |         } | ||||||
|  |         return same; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public enum PositionFetchMore { | ||||||
|  |         TOP, | ||||||
|  |         BOTTOM | ||||||
|  |     } | ||||||
|  | 
 | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -58,7 +58,7 @@ public class Notification { | ||||||
| 
 | 
 | ||||||
|     public transient List<Notification> relatedNotifications; |     public transient List<Notification> relatedNotifications; | ||||||
|     public boolean isFetchMore; |     public boolean isFetchMore; | ||||||
|     public boolean isFetchMoreHidden = false; | 
 | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * Serialized a list of Notification class |      * Serialized a list of Notification class | ||||||
|  |  | ||||||
|  | @ -28,6 +28,8 @@ import java.util.Date; | ||||||
| import java.util.List; | import java.util.List; | ||||||
| 
 | 
 | ||||||
| import app.fedilab.android.activities.MainActivity; | import app.fedilab.android.activities.MainActivity; | ||||||
|  | import app.fedilab.android.client.entities.api.Conversation; | ||||||
|  | import app.fedilab.android.client.entities.api.Conversations; | ||||||
| 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.Pagination; | ||||||
|  | @ -57,6 +59,8 @@ public class StatusCache { | ||||||
|     public Status status; |     public Status status; | ||||||
|     @SerializedName("notification") |     @SerializedName("notification") | ||||||
|     public Notification notification; |     public Notification notification; | ||||||
|  |     @SerializedName("conversation") | ||||||
|  |     public Conversation conversation; | ||||||
|     @SerializedName("created_at") |     @SerializedName("created_at") | ||||||
|     public Date created_at; |     public Date created_at; | ||||||
|     @SerializedName("updated_at") |     @SerializedName("updated_at") | ||||||
|  | @ -103,6 +107,21 @@ public class StatusCache { | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     /** | ||||||
|  |      * Serialized a Conversation class | ||||||
|  |      * | ||||||
|  |      * @param mastodon_conversation {@link Conversation} to serialize | ||||||
|  |      * @return String serialized Conversation | ||||||
|  |      */ | ||||||
|  |     public static String mastodonConversationToStringStorage(Conversation mastodon_conversation) { | ||||||
|  |         Gson gson = new Gson(); | ||||||
|  |         try { | ||||||
|  |             return gson.toJson(mastodon_conversation); | ||||||
|  |         } catch (Exception e) { | ||||||
|  |             return null; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     /** |     /** | ||||||
|      * Unserialized a Mastodon Status |      * Unserialized a Mastodon Status | ||||||
|      * |      * | ||||||
|  | @ -135,6 +154,23 @@ public class StatusCache { | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Unserialized a Mastodon Conversation | ||||||
|  |      * | ||||||
|  |      * @param serializedConversation String serialized Conversation | ||||||
|  |      * @return {@link Conversation} | ||||||
|  |      */ | ||||||
|  |     public static Conversation restoreConversationFromString(String serializedConversation) { | ||||||
|  |         Gson gson = new Gson(); | ||||||
|  |         try { | ||||||
|  |             return gson.fromJson(serializedConversation, Conversation.class); | ||||||
|  |         } catch (Exception e) { | ||||||
|  |             e.printStackTrace(); | ||||||
|  |             return null; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     /** |     /** | ||||||
|      * Insert or update a status |      * Insert or update a status | ||||||
|      * |      * | ||||||
|  | @ -238,6 +274,9 @@ public class StatusCache { | ||||||
|         if (statusCache.notification != null) { |         if (statusCache.notification != null) { | ||||||
|             values.put(Sqlite.COL_STATUS, mastodonNotificationToStringStorage(statusCache.notification)); |             values.put(Sqlite.COL_STATUS, mastodonNotificationToStringStorage(statusCache.notification)); | ||||||
|         } |         } | ||||||
|  |         if (statusCache.conversation != null) { | ||||||
|  |             values.put(Sqlite.COL_STATUS, mastodonConversationToStringStorage(statusCache.conversation)); | ||||||
|  |         } | ||||||
|         values.put(Sqlite.COL_CREATED_AT, Helper.dateToString(new Date())); |         values.put(Sqlite.COL_CREATED_AT, Helper.dateToString(new Date())); | ||||||
|         //Inserts token |         //Inserts token | ||||||
|         try { |         try { | ||||||
|  | @ -268,6 +307,9 @@ public class StatusCache { | ||||||
|         if (statusCache.notification != null) { |         if (statusCache.notification != null) { | ||||||
|             values.put(Sqlite.COL_STATUS, mastodonNotificationToStringStorage(statusCache.notification)); |             values.put(Sqlite.COL_STATUS, mastodonNotificationToStringStorage(statusCache.notification)); | ||||||
|         } |         } | ||||||
|  |         if (statusCache.conversation != null) { | ||||||
|  |             values.put(Sqlite.COL_STATUS, mastodonConversationToStringStorage(statusCache.conversation)); | ||||||
|  |         } | ||||||
|         values.put(Sqlite.COL_UPDATED_AT, Helper.dateToString(new Date())); |         values.put(Sqlite.COL_UPDATED_AT, Helper.dateToString(new Date())); | ||||||
|         //Inserts token |         //Inserts token | ||||||
|         try { |         try { | ||||||
|  | @ -388,6 +430,42 @@ public class StatusCache { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |     /** | ||||||
|  |      * Get paginated conversations from db | ||||||
|  |      * | ||||||
|  |      * @param instance String - instance | ||||||
|  |      * @param user_id  String - us | ||||||
|  |      * @param max_id   String - status having max id | ||||||
|  |      * @param min_id   String - status having min id | ||||||
|  |      * @return Statuses | ||||||
|  |      * @throws DBException - throws a db exception | ||||||
|  |      */ | ||||||
|  |     public Conversations getConversations(String instance, String user_id, String max_id, String min_id, String since_id) throws DBException { | ||||||
|  |         if (db == null) { | ||||||
|  |             throw new DBException("db is null. Wrong initialization."); | ||||||
|  |         } | ||||||
|  |         String order = " DESC"; | ||||||
|  |         String selection = Sqlite.COL_INSTANCE + "='" + instance + "' AND " + Sqlite.COL_USER_ID + "= '" + user_id + "' AND " + Sqlite.COL_TYPE + "= '" + Timeline.TimeLineEnum.CONVERSATION.getValue() + "' "; | ||||||
|  |         String limit = String.valueOf(MastodonHelper.statusesPerCall(context)); | ||||||
|  |         if (min_id != null) { | ||||||
|  |             selection += "AND " + Sqlite.COL_STATUS_ID + " > '" + min_id + "' "; | ||||||
|  |             order = " ASC"; | ||||||
|  |         } else if (max_id != null) { | ||||||
|  |             selection += "AND " + Sqlite.COL_STATUS_ID + " < '" + max_id + "' "; | ||||||
|  |         } else if (since_id != null) { | ||||||
|  |             selection += "AND " + Sqlite.COL_STATUS_ID + " > '" + since_id + "' "; | ||||||
|  |             limit = null; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         try { | ||||||
|  |             Cursor c = db.query(Sqlite.TABLE_STATUS_CACHE, null, selection, null, null, null, Sqlite.COL_STATUS_ID + order, limit); | ||||||
|  |             return createConversationReply(cursorToListOfConversations(c)); | ||||||
|  |         } catch (Exception e) { | ||||||
|  |             e.printStackTrace(); | ||||||
|  |             return null; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     /** |     /** | ||||||
|      * Get paginated statuses from db |      * Get paginated statuses from db | ||||||
|      * |      * | ||||||
|  | @ -495,7 +573,7 @@ public class StatusCache { | ||||||
|      * Convert a cursor to list of notifications |      * Convert a cursor to list of notifications | ||||||
|      * |      * | ||||||
|      * @param c Cursor |      * @param c Cursor | ||||||
|      * @return List<Status> |      * @return List<Notification> | ||||||
|      */ |      */ | ||||||
|     private List<Notification> cursorToListOfNotifications(Cursor c) { |     private List<Notification> cursorToListOfNotifications(Cursor c) { | ||||||
|         //No element found |         //No element found | ||||||
|  | @ -514,6 +592,28 @@ public class StatusCache { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |     /** | ||||||
|  |      * Convert a cursor to list of Conversation | ||||||
|  |      * | ||||||
|  |      * @param c Cursor | ||||||
|  |      * @return List<Conversation> | ||||||
|  |      */ | ||||||
|  |     private List<Conversation> cursorToListOfConversations(Cursor c) { | ||||||
|  |         //No element found | ||||||
|  |         if (c.getCount() == 0) { | ||||||
|  |             c.close(); | ||||||
|  |             return null; | ||||||
|  |         } | ||||||
|  |         List<Conversation> conversationList = new ArrayList<>(); | ||||||
|  |         while (c.moveToNext()) { | ||||||
|  |             Conversation conversation = convertCursorToConversation(c); | ||||||
|  |             conversationList.add(conversation); | ||||||
|  |         } | ||||||
|  |         //Close the cursor | ||||||
|  |         c.close(); | ||||||
|  |         return conversationList; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     /** |     /** | ||||||
|      * Create a reply from db in the same way than API call |      * Create a reply from db in the same way than API call | ||||||
|      * |      * | ||||||
|  | @ -537,6 +637,30 @@ public class StatusCache { | ||||||
|         return notifications; |         return notifications; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Create a reply from db in the same way than API call | ||||||
|  |      * | ||||||
|  |      * @param conversationList List<Conversation> | ||||||
|  |      * @return Conversations (with pagination) | ||||||
|  |      */ | ||||||
|  |     private Conversations createConversationReply(List<Conversation> conversationList) { | ||||||
|  |         Conversations conversations = new Conversations(); | ||||||
|  |         conversations.conversations = conversationList; | ||||||
|  |         Pagination pagination = new Pagination(); | ||||||
|  |         if (conversationList != null && conversationList.size() > 0) { | ||||||
|  |             //Status list is inverted, it happens for min_id due to ASC ordering | ||||||
|  |             if (conversationList.get(0).id.compareTo(conversationList.get(conversationList.size() - 1).id) < 0) { | ||||||
|  |                 Collections.reverse(conversationList); | ||||||
|  |                 conversations.conversations = conversationList; | ||||||
|  |             } | ||||||
|  |             pagination.max_id = conversationList.get(0).id; | ||||||
|  |             pagination.min_id = conversationList.get(conversationList.size() - 1).id; | ||||||
|  |         } | ||||||
|  |         conversations.pagination = pagination; | ||||||
|  |         return conversations; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     /** |     /** | ||||||
|      * Create a reply from db in the same way than API call |      * Create a reply from db in the same way than API call | ||||||
|      * |      * | ||||||
|  | @ -583,6 +707,17 @@ public class StatusCache { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |     /** | ||||||
|  |      * Read cursor and hydrate without closing it | ||||||
|  |      * | ||||||
|  |      * @param c - Cursor | ||||||
|  |      * @return Conversation | ||||||
|  |      */ | ||||||
|  |     private Conversation convertCursorToConversation(Cursor c) { | ||||||
|  |         String serializedNotification = c.getString(c.getColumnIndexOrThrow(Sqlite.COL_STATUS)); | ||||||
|  |         return restoreConversationFromString(serializedNotification); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     public enum order { |     public enum order { | ||||||
|         @SerializedName("ASC") |         @SerializedName("ASC") | ||||||
|         ASC("ASC"), |         ASC("ASC"), | ||||||
|  |  | ||||||
|  | @ -358,6 +358,8 @@ public class Timeline { | ||||||
|         DIRECT("DIRECT"), |         DIRECT("DIRECT"), | ||||||
|         @SerializedName("NOTIFICATION") |         @SerializedName("NOTIFICATION") | ||||||
|         NOTIFICATION("NOTIFICATION"), |         NOTIFICATION("NOTIFICATION"), | ||||||
|  |         @SerializedName("CONVERSATION") | ||||||
|  |         CONVERSATION("CONVERSATION"), | ||||||
|         @SerializedName("LOCAL") |         @SerializedName("LOCAL") | ||||||
|         LOCAL("LOCAL"), |         LOCAL("LOCAL"), | ||||||
|         @SerializedName("PUBLIC") |         @SerializedName("PUBLIC") | ||||||
|  |  | ||||||
|  | @ -58,6 +58,7 @@ public class ConversationAdapter extends RecyclerView.Adapter<RecyclerView.ViewH | ||||||
|     private final List<Conversation> conversationList; |     private final List<Conversation> conversationList; | ||||||
|     private Context context; |     private Context context; | ||||||
|     private boolean isExpended = false; |     private boolean isExpended = false; | ||||||
|  |     public FetchMoreCallBack fetchMoreCallBack; | ||||||
| 
 | 
 | ||||||
|     public ConversationAdapter(List<Conversation> conversations) { |     public ConversationAdapter(List<Conversation> conversations) { | ||||||
|         if (conversations == null) { |         if (conversations == null) { | ||||||
|  | @ -127,6 +128,42 @@ public class ConversationAdapter extends RecyclerView.Adapter<RecyclerView.ViewH | ||||||
|         if (conversation.last_status == null) { |         if (conversation.last_status == null) { | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|  |         if (conversation.isFetchMore && fetchMoreCallBack != null) { | ||||||
|  |             holder.binding.layoutFetchMore.fetchMoreContainer.setVisibility(View.VISIBLE); | ||||||
|  |             holder.binding.layoutFetchMore.fetchMoreMin.setOnClickListener(v -> { | ||||||
|  |                 conversation.isFetchMore = false; | ||||||
|  |                 if (holder.getBindingAdapterPosition() < conversationList.size() - 1) { | ||||||
|  |                     String fromId; | ||||||
|  |                     if (conversation.positionFetchMore == Conversation.PositionFetchMore.TOP) { | ||||||
|  |                         fromId = conversationList.get(position + 1).id; | ||||||
|  |                     } else { | ||||||
|  |                         fromId = conversation.id; | ||||||
|  |                     } | ||||||
|  |                     fetchMoreCallBack.onClickMinId(fromId, conversation); | ||||||
|  |                     notifyItemChanged(position); | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |             }); | ||||||
|  |             holder.binding.layoutFetchMore.fetchMoreMax.setOnClickListener(v -> { | ||||||
|  |                 //We hide the button | ||||||
|  |                 conversation.isFetchMore = false; | ||||||
|  |                 String fromId; | ||||||
|  |                 if (conversation.positionFetchMore == Conversation.PositionFetchMore.TOP) { | ||||||
|  |                     fromId = conversationList.get(position).id; | ||||||
|  |                 } else { | ||||||
|  |                     fromId = conversationList.get(position - 1).id; | ||||||
|  |                 } | ||||||
|  |                 notifyItemChanged(position); | ||||||
|  |                 fetchMoreCallBack.onClickMaxId(fromId, conversation); | ||||||
|  |             }); | ||||||
|  |         } else { | ||||||
|  |             holder.binding.layoutFetchMore.fetchMoreContainer.setVisibility(View.GONE); | ||||||
|  |         } | ||||||
|  |         if (conversation.cached) { | ||||||
|  |             holder.binding.cacheIndicator.setVisibility(View.VISIBLE); | ||||||
|  |         } else { | ||||||
|  |             holder.binding.cacheIndicator.setVisibility(View.GONE); | ||||||
|  |         } | ||||||
|         //---- SPOILER TEXT ----- |         //---- SPOILER TEXT ----- | ||||||
|         boolean expand_cw = sharedpreferences.getBoolean(context.getString(R.string.SET_EXPAND_CW), false); |         boolean expand_cw = sharedpreferences.getBoolean(context.getString(R.string.SET_EXPAND_CW), false); | ||||||
|         if (conversation.last_status.spoiler_text != null && !conversation.last_status.spoiler_text.trim().isEmpty()) { |         if (conversation.last_status.spoiler_text != null && !conversation.last_status.spoiler_text.trim().isEmpty()) { | ||||||
|  | @ -257,5 +294,9 @@ public class ConversationAdapter extends RecyclerView.Adapter<RecyclerView.ViewH | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     public interface FetchMoreCallBack { | ||||||
|  |         void onClickMinId(String min_id, Conversation conversationToUpdate); | ||||||
| 
 | 
 | ||||||
|  |         void onClickMaxId(String max_id, Conversation conversationToUpdate); | ||||||
|  |     } | ||||||
| } | } | ||||||
|  | @ -15,6 +15,7 @@ package app.fedilab.android.ui.fragment.timeline; | ||||||
|  * see <http://www.gnu.org/licenses>. */ |  * see <http://www.gnu.org/licenses>. */ | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | import android.content.SharedPreferences; | ||||||
| import android.os.Bundle; | import android.os.Bundle; | ||||||
| import android.view.LayoutInflater; | import android.view.LayoutInflater; | ||||||
| import android.view.View; | import android.view.View; | ||||||
|  | @ -24,6 +25,7 @@ import androidx.annotation.NonNull; | ||||||
| import androidx.annotation.Nullable; | import androidx.annotation.Nullable; | ||||||
| import androidx.fragment.app.Fragment; | import androidx.fragment.app.Fragment; | ||||||
| import androidx.lifecycle.ViewModelProvider; | import androidx.lifecycle.ViewModelProvider; | ||||||
|  | import androidx.preference.PreferenceManager; | ||||||
| import androidx.recyclerview.widget.LinearLayoutManager; | import androidx.recyclerview.widget.LinearLayoutManager; | ||||||
| import androidx.recyclerview.widget.RecyclerView; | import androidx.recyclerview.widget.RecyclerView; | ||||||
| 
 | 
 | ||||||
|  | @ -34,33 +36,149 @@ import app.fedilab.android.BaseMainActivity; | ||||||
| import app.fedilab.android.R; | import app.fedilab.android.R; | ||||||
| import app.fedilab.android.client.entities.api.Conversation; | import app.fedilab.android.client.entities.api.Conversation; | ||||||
| import app.fedilab.android.client.entities.api.Conversations; | import app.fedilab.android.client.entities.api.Conversations; | ||||||
|  | import app.fedilab.android.client.entities.app.StatusCache; | ||||||
|  | import app.fedilab.android.client.entities.app.Timeline; | ||||||
| import app.fedilab.android.databinding.FragmentPaginationBinding; | import app.fedilab.android.databinding.FragmentPaginationBinding; | ||||||
|  | import app.fedilab.android.exception.DBException; | ||||||
| import app.fedilab.android.helper.MastodonHelper; | import app.fedilab.android.helper.MastodonHelper; | ||||||
| import app.fedilab.android.helper.ThemeHelper; | import app.fedilab.android.helper.ThemeHelper; | ||||||
| import app.fedilab.android.ui.drawer.ConversationAdapter; | import app.fedilab.android.ui.drawer.ConversationAdapter; | ||||||
| import app.fedilab.android.viewmodel.mastodon.TimelinesVM; | import app.fedilab.android.viewmodel.mastodon.TimelinesVM; | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| public class FragmentMastodonConversation extends Fragment { | public class FragmentMastodonConversation extends Fragment implements ConversationAdapter.FetchMoreCallBack { | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|     private FragmentPaginationBinding binding; |     private FragmentPaginationBinding binding; | ||||||
|     private TimelinesVM timelinesVM; |     private TimelinesVM timelinesVM; | ||||||
|     private FragmentMastodonConversation currentFragment; |  | ||||||
|     private boolean flagLoading; |     private boolean flagLoading; | ||||||
|     private List<Conversation> conversations; |     private List<Conversation> conversationList; | ||||||
|     private String max_id; |     private String max_id, min_id, min_id_fetch_more, max_id_fetch_more; | ||||||
|     private ConversationAdapter conversationAdapter; |     private ConversationAdapter conversationAdapter; | ||||||
|  |     private LinearLayoutManager mLayoutManager; | ||||||
| 
 | 
 | ||||||
|     public View onCreateView(@NonNull LayoutInflater inflater, |     public View onCreateView(@NonNull LayoutInflater inflater, | ||||||
|                              ViewGroup container, Bundle savedInstanceState) { |                              ViewGroup container, Bundle savedInstanceState) { | ||||||
|         currentFragment = this; |  | ||||||
|         flagLoading = false; |         flagLoading = false; | ||||||
|         binding = FragmentPaginationBinding.inflate(inflater, container, false); |         binding = FragmentPaginationBinding.inflate(inflater, container, false); | ||||||
|         binding.getRoot().setBackgroundColor(ThemeHelper.getBackgroundColor(requireActivity())); |         binding.getRoot().setBackgroundColor(ThemeHelper.getBackgroundColor(requireActivity())); | ||||||
|         return binding.getRoot(); |         return binding.getRoot(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     /** | ||||||
|  |      * 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) { | ||||||
|  |         route(direction, fetchingMissing, null); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * 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, Conversation conversationToUpdate) { | ||||||
|  |         if (binding == null || !isAdded() || getActivity() == null) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |         if (!isAdded()) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(requireActivity()); | ||||||
|  |         boolean useCache = sharedpreferences.getBoolean(getString(R.string.SET_USE_CACHE), true); | ||||||
|  | 
 | ||||||
|  |         TimelinesVM.TimelineParams timelineParams = new TimelinesVM.TimelineParams(Timeline.TimeLineEnum.NOTIFICATION, direction, null); | ||||||
|  |         timelineParams.limit = MastodonHelper.notificationsPerCall(requireActivity()); | ||||||
|  |         if (direction == FragmentMastodonTimeline.DIRECTION.REFRESH || direction == FragmentMastodonTimeline.DIRECTION.SCROLL_TOP) { | ||||||
|  |             timelineParams.maxId = null; | ||||||
|  |             timelineParams.minId = null; | ||||||
|  |         } else if (direction == FragmentMastodonTimeline.DIRECTION.BOTTOM) { | ||||||
|  |             timelineParams.maxId = fetchingMissing ? max_id_fetch_more : max_id; | ||||||
|  |             timelineParams.minId = null; | ||||||
|  |         } else if (direction == FragmentMastodonTimeline.DIRECTION.TOP) { | ||||||
|  |             timelineParams.minId = fetchingMissing ? min_id_fetch_more : min_id; | ||||||
|  |             timelineParams.maxId = null; | ||||||
|  |         } else { | ||||||
|  |             timelineParams.maxId = max_id; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         timelineParams.fetchingMissing = fetchingMissing; | ||||||
|  | 
 | ||||||
|  |         if (useCache) { | ||||||
|  |             getCachedConversations(direction, fetchingMissing, timelineParams); | ||||||
|  |         } else { | ||||||
|  |             getLiveConversations(direction, fetchingMissing, timelineParams, conversationToUpdate); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private void getCachedConversations(FragmentMastodonTimeline.DIRECTION direction, boolean fetchingMissing, TimelinesVM.TimelineParams timelineParams) { | ||||||
|  | 
 | ||||||
|  |         if (direction == null) { | ||||||
|  |             timelinesVM.getConversations(conversationList, timelineParams) | ||||||
|  |                     .observe(getViewLifecycleOwner(), conversationsCached -> { | ||||||
|  |                         if (conversationsCached == null || conversationsCached.conversations == null || conversationsCached.conversations.size() == 0) { | ||||||
|  |                             getLiveConversations(null, fetchingMissing, timelineParams, null); | ||||||
|  |                         } else { | ||||||
|  |                             initializeConversationCommonView(conversationsCached); | ||||||
|  |                         } | ||||||
|  |                     }); | ||||||
|  |         } else if (direction == FragmentMastodonTimeline.DIRECTION.BOTTOM) { | ||||||
|  |             timelinesVM.getConversationsCache(conversationList, timelineParams) | ||||||
|  |                     .observe(getViewLifecycleOwner(), conversationsBottom -> { | ||||||
|  |                         if (conversationsBottom == null || conversationsBottom.conversations == null || conversationsBottom.conversations.size() == 0) { | ||||||
|  |                             getLiveConversations(FragmentMastodonTimeline.DIRECTION.BOTTOM, fetchingMissing, timelineParams, null); | ||||||
|  |                         } else { | ||||||
|  |                             dealWithPagination(conversationsBottom, FragmentMastodonTimeline.DIRECTION.BOTTOM, fetchingMissing, null); | ||||||
|  |                         } | ||||||
|  | 
 | ||||||
|  |                     }); | ||||||
|  |         } else if (direction == FragmentMastodonTimeline.DIRECTION.TOP) { | ||||||
|  |             timelinesVM.getConversationsCache(conversationList, timelineParams) | ||||||
|  |                     .observe(getViewLifecycleOwner(), conversationsTop -> { | ||||||
|  |                         if (conversationsTop == null || conversationsTop.conversations == null || conversationsTop.conversations.size() == 0) { | ||||||
|  |                             getLiveConversations(FragmentMastodonTimeline.DIRECTION.TOP, fetchingMissing, timelineParams, null); | ||||||
|  |                         } else { | ||||||
|  |                             dealWithPagination(conversationsTop, FragmentMastodonTimeline.DIRECTION.TOP, fetchingMissing, null); | ||||||
|  |                         } | ||||||
|  |                     }); | ||||||
|  |         } else if (direction == FragmentMastodonTimeline.DIRECTION.REFRESH) { | ||||||
|  |             timelinesVM.getConversations(conversationList, timelineParams) | ||||||
|  |                     .observe(getViewLifecycleOwner(), notificationsRefresh -> { | ||||||
|  |                         if (conversationAdapter != null) { | ||||||
|  |                             dealWithPagination(notificationsRefresh, FragmentMastodonTimeline.DIRECTION.REFRESH, true, null); | ||||||
|  |                         } else { | ||||||
|  |                             initializeConversationCommonView(notificationsRefresh); | ||||||
|  |                         } | ||||||
|  |                     }); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private void getLiveConversations(FragmentMastodonTimeline.DIRECTION direction, boolean fetchingMissing, TimelinesVM.TimelineParams timelineParams, Conversation conversationToUpdate) { | ||||||
|  |         if (direction == null) { | ||||||
|  |             timelinesVM.getConversations(conversationList, timelineParams) | ||||||
|  |                     .observe(getViewLifecycleOwner(), this::initializeConversationCommonView); | ||||||
|  |         } else if (direction == FragmentMastodonTimeline.DIRECTION.BOTTOM) { | ||||||
|  |             timelinesVM.getConversations(conversationList, timelineParams) | ||||||
|  |                     .observe(getViewLifecycleOwner(), conversationsBottom -> dealWithPagination(conversationsBottom, FragmentMastodonTimeline.DIRECTION.BOTTOM, fetchingMissing, conversationToUpdate)); | ||||||
|  |         } else if (direction == FragmentMastodonTimeline.DIRECTION.TOP) { | ||||||
|  |             timelinesVM.getConversations(conversationList, timelineParams) | ||||||
|  |                     .observe(getViewLifecycleOwner(), conversationsTop -> dealWithPagination(conversationsTop, FragmentMastodonTimeline.DIRECTION.TOP, fetchingMissing, conversationToUpdate)); | ||||||
|  |         } else if (direction == FragmentMastodonTimeline.DIRECTION.REFRESH) { | ||||||
|  |             timelinesVM.getConversations(conversationList, timelineParams) | ||||||
|  |                     .observe(getViewLifecycleOwner(), conversationsRefresh -> { | ||||||
|  |                         if (conversationAdapter != null) { | ||||||
|  |                             dealWithPagination(conversationsRefresh, FragmentMastodonTimeline.DIRECTION.REFRESH, true, conversationToUpdate); | ||||||
|  |                         } else { | ||||||
|  |                             initializeConversationCommonView(conversationsRefresh); | ||||||
|  |                         } | ||||||
|  |                     }); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|     @Override |     @Override | ||||||
|     public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { |     public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { | ||||||
|         super.onViewCreated(view, savedInstanceState); |         super.onViewCreated(view, savedInstanceState); | ||||||
|  | @ -74,8 +192,7 @@ public class FragmentMastodonConversation extends Fragment { | ||||||
|         binding.recyclerView.setVisibility(View.GONE); |         binding.recyclerView.setVisibility(View.GONE); | ||||||
|         timelinesVM = new ViewModelProvider(FragmentMastodonConversation.this).get(TimelinesVM.class); |         timelinesVM = new ViewModelProvider(FragmentMastodonConversation.this).get(TimelinesVM.class); | ||||||
|         max_id = null; |         max_id = null; | ||||||
|         timelinesVM.getConversations(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, null, null, null, MastodonHelper.statusesPerCall(requireActivity())) |         route(null, false); | ||||||
|                 .observe(getViewLifecycleOwner(), this::initializeConversationCommonView); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|  | @ -85,71 +202,214 @@ public class FragmentMastodonConversation extends Fragment { | ||||||
|      */ |      */ | ||||||
|     private void initializeConversationCommonView(final Conversations conversations) { |     private void initializeConversationCommonView(final Conversations conversations) { | ||||||
|         flagLoading = false; |         flagLoading = false; | ||||||
|  |         if (binding == null || !isAdded() || getActivity() == null) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|         binding.loader.setVisibility(View.GONE); |         binding.loader.setVisibility(View.GONE); | ||||||
|         binding.noAction.setVisibility(View.GONE); |         binding.noAction.setVisibility(View.GONE); | ||||||
|         if (conversationAdapter != null && this.conversations != null) { |         binding.swipeContainer.setRefreshing(false); | ||||||
|             int size = this.conversations.size(); |         binding.swipeContainer.setOnRefreshListener(() -> { | ||||||
|             this.conversations.clear(); |             binding.swipeContainer.setRefreshing(true); | ||||||
|             this.conversations = new ArrayList<>(); |             flagLoading = false; | ||||||
|  |             route(FragmentMastodonTimeline.DIRECTION.REFRESH, true); | ||||||
|  |         }); | ||||||
|  |         if (conversations == null || conversations.conversations == null || conversations.conversations.size() == 0) { | ||||||
|  |             binding.noAction.setVisibility(View.VISIBLE); | ||||||
|  |             binding.recyclerView.setVisibility(View.GONE); | ||||||
|  |             return; | ||||||
|  |         } else { | ||||||
|  |             binding.noAction.setVisibility(View.GONE); | ||||||
|  |             binding.recyclerView.setVisibility(View.VISIBLE); | ||||||
|  |         } | ||||||
|  |         flagLoading = conversations.pagination.max_id == null; | ||||||
|  | 
 | ||||||
|  |         if (conversationAdapter != null && conversationList != null) { | ||||||
|  |             int size = conversationList.size(); | ||||||
|  |             conversationList.clear(); | ||||||
|  |             conversationList = new ArrayList<>(); | ||||||
|             conversationAdapter.notifyItemRangeRemoved(0, size); |             conversationAdapter.notifyItemRangeRemoved(0, size); | ||||||
|         } |         } | ||||||
|         binding.recyclerView.setVisibility(View.VISIBLE); |         if (conversationList == null) { | ||||||
|         this.conversations = conversations.conversations; |             conversationList = new ArrayList<>(); | ||||||
|         conversationAdapter = new ConversationAdapter(this.conversations); |         } | ||||||
|         //conversationAdapter.itemListener = currentFragment; |         conversationList.addAll(conversations.conversations); | ||||||
|         binding.swipeContainer.setRefreshing(false); | 
 | ||||||
|         LinearLayoutManager mLayoutManager = new LinearLayoutManager(requireActivity()); |         if (max_id == null || (conversations.pagination.max_id != null && conversations.pagination.max_id.compareTo(max_id) < 0)) { | ||||||
|  |             max_id = conversations.pagination.max_id; | ||||||
|  |         } | ||||||
|  |         if (min_id == null || (conversations.pagination.min_id != null && conversations.pagination.min_id.compareTo(min_id) > 0)) { | ||||||
|  |             min_id = conversations.pagination.min_id; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         conversationAdapter = new ConversationAdapter(conversationList); | ||||||
|  |         conversationAdapter.fetchMoreCallBack = this; | ||||||
|  |         mLayoutManager = new LinearLayoutManager(requireActivity()); | ||||||
|         binding.recyclerView.setLayoutManager(mLayoutManager); |         binding.recyclerView.setLayoutManager(mLayoutManager); | ||||||
|         binding.recyclerView.setAdapter(conversationAdapter); |         binding.recyclerView.setAdapter(conversationAdapter); | ||||||
|         max_id = conversations.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(); | ||||||
|                     int totalItemCount = mLayoutManager.getItemCount(); |                     int totalItemCount = mLayoutManager.getItemCount(); | ||||||
|  | 
 | ||||||
|                     if (firstVisibleItem + visibleItemCount == totalItemCount) { |                     if (firstVisibleItem + visibleItemCount == totalItemCount) { | ||||||
|                         if (!flagLoading) { |                         if (!flagLoading) { | ||||||
|                             flagLoading = true; |                             flagLoading = true; | ||||||
|                             binding.loadingNextElements.setVisibility(View.VISIBLE); |                             binding.loadingNextElements.setVisibility(View.VISIBLE); | ||||||
|                             timelinesVM.getConversations(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, max_id, null, null, MastodonHelper.statusesPerCall(requireActivity())) |                             route(FragmentMastodonTimeline.DIRECTION.BOTTOM, false); | ||||||
|                                     .observe(FragmentMastodonConversation.this, fetched_conversations -> { |  | ||||||
|                                         binding.loadingNextElements.setVisibility(View.GONE); |  | ||||||
|                                         if (currentFragment.conversations != null && fetched_conversations != null) { |  | ||||||
|                                             int startId = 0; |  | ||||||
|                                             flagLoading = fetched_conversations.pagination.max_id == null; |  | ||||||
|                                             //There are some statuses present in the timeline |  | ||||||
|                                             if (currentFragment.conversations.size() > 0) { |  | ||||||
|                                                 startId = currentFragment.conversations.size(); |  | ||||||
|                                             } |  | ||||||
|                                             currentFragment.conversations.addAll(fetched_conversations.conversations); |  | ||||||
|                                             max_id = fetched_conversations.pagination.max_id; |  | ||||||
|                                             conversationAdapter.notifyItemRangeInserted(startId, fetched_conversations.conversations.size()); |  | ||||||
|                                         } |  | ||||||
|                                     }); |  | ||||||
|                         } |                         } | ||||||
|                     } 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); | ||||||
|  |                         route(FragmentMastodonTimeline.DIRECTION.TOP, false); | ||||||
|  |                     } | ||||||
|                 } |                 } | ||||||
| 
 |  | ||||||
|             } |             } | ||||||
|         }); |         }); | ||||||
|         binding.swipeContainer.setOnRefreshListener(() -> { | 
 | ||||||
|             if (this.conversations != null && this.conversations.size() > 0) { |     } | ||||||
|                 binding.swipeContainer.setRefreshing(true); | 
 | ||||||
|                 max_id = null; | 
 | ||||||
|  |     /** | ||||||
|  |      * Update view and pagination when scrolling down | ||||||
|  |      * | ||||||
|  |      * @param fetched_conversations Conversations | ||||||
|  |      */ | ||||||
|  |     private synchronized void dealWithPagination(Conversations fetched_conversations, FragmentMastodonTimeline.DIRECTION direction, boolean fetchingMissing, Conversation conversationToUpdate) { | ||||||
|  |         if (binding == null || !isAdded() || getActivity() == null) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |         binding.swipeContainer.setRefreshing(false); | ||||||
|  |         binding.loadingNextElements.setVisibility(View.GONE); | ||||||
|         flagLoading = false; |         flagLoading = false; | ||||||
|                 timelinesVM.getConversations(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, null, null, null, MastodonHelper.statusesPerCall(requireActivity())) |         if (conversationList != null && fetched_conversations != null && fetched_conversations.conversations != null && fetched_conversations.conversations.size() > 0) { | ||||||
|                         .observe(FragmentMastodonConversation.this, this::initializeConversationCommonView); |             try { | ||||||
|  |                 if (conversationToUpdate != null) { | ||||||
|  |                     new Thread(() -> { | ||||||
|  |                         StatusCache statusCache = new StatusCache(); | ||||||
|  |                         statusCache.instance = BaseMainActivity.currentInstance; | ||||||
|  |                         statusCache.user_id = BaseMainActivity.currentUserID; | ||||||
|  |                         conversationToUpdate.isFetchMore = false; | ||||||
|  |                         statusCache.conversation = conversationToUpdate; | ||||||
|  |                         statusCache.status_id = conversationToUpdate.id; | ||||||
|  |                         try { | ||||||
|  |                             new StatusCache(requireActivity()).updateIfExists(statusCache); | ||||||
|  |                         } catch (DBException e) { | ||||||
|  |                             e.printStackTrace(); | ||||||
|  |                         } | ||||||
|  |                     }).start(); | ||||||
|  |                 } | ||||||
|  |             } catch (Exception ignored) { | ||||||
|             } |             } | ||||||
|         }); |  | ||||||
| 
 | 
 | ||||||
|  |             flagLoading = fetched_conversations.pagination.max_id == null; | ||||||
|  |             binding.noAction.setVisibility(View.GONE); | ||||||
|  |             //Update the timeline with new statuses | ||||||
|  |             updateConversationListWith(fetched_conversations.conversations); | ||||||
|  |             if (direction == FragmentMastodonTimeline.DIRECTION.TOP && fetchingMissing) { | ||||||
|  |                 binding.recyclerView.scrollToPosition(getPosition(fetched_conversations.conversations.get(fetched_conversations.conversations.size() - 1)) + 1); | ||||||
|             } |             } | ||||||
|  |             if (!fetchingMissing) { | ||||||
|  |                 if (fetched_conversations.pagination.max_id == null) { | ||||||
|  |                     flagLoading = true; | ||||||
|  |                 } else if (max_id == null || fetched_conversations.pagination.max_id.compareTo(max_id) < 0) { | ||||||
|  |                     max_id = fetched_conversations.pagination.max_id; | ||||||
|  |                 } | ||||||
|  |                 if (min_id == null || (fetched_conversations.pagination.min_id != null && fetched_conversations.pagination.min_id.compareTo(min_id) > 0)) { | ||||||
|  |                     min_id = fetched_conversations.pagination.min_id; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } else if (direction == FragmentMastodonTimeline.DIRECTION.BOTTOM) { | ||||||
|  |             flagLoading = true; | ||||||
|  |         } | ||||||
|  |         if (direction == FragmentMastodonTimeline.DIRECTION.SCROLL_TOP) { | ||||||
|  |             binding.recyclerView.scrollToPosition(0); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Return the position of the convnersation in the ArrayList | ||||||
|  |      * | ||||||
|  |      * @param conversation - Conversation to fetch | ||||||
|  |      * @return position or -1 if not found | ||||||
|  |      */ | ||||||
|  |     private int getPosition(Conversation conversation) { | ||||||
|  |         int position = 0; | ||||||
|  |         boolean found = false; | ||||||
|  |         for (Conversation _conversation : conversationList) { | ||||||
|  |             if (conversation != null && _conversation.id.compareTo(conversation.id) == 0) { | ||||||
|  |                 found = true; | ||||||
|  |                 break; | ||||||
|  |             } | ||||||
|  |             position++; | ||||||
|  |         } | ||||||
|  |         return found ? position : -1; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Update the timeline with received Conversations | ||||||
|  |      * | ||||||
|  |      * @param conversationsReceived - List<Conversation> Conversation received | ||||||
|  |      */ | ||||||
|  |     private void updateConversationListWith(List<Conversation> conversationsReceived) { | ||||||
|  |         if (conversationsReceived != null && conversationsReceived.size() > 0) { | ||||||
|  |             for (Conversation conversationReceived : conversationsReceived) { | ||||||
|  |                 int position = 0; | ||||||
|  |                 //We loop through messages already in the timeline | ||||||
|  |                 if (conversationList != null) { | ||||||
|  |                     conversationAdapter.notifyItemRangeChanged(0, conversationList.size()); | ||||||
|  |                     for (Conversation conversationsAlreadyPresent : conversationList) { | ||||||
|  |                         //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 (conversationReceived.id.compareTo(conversationsAlreadyPresent.id) > 0) { | ||||||
|  |                             if (!conversationList.contains(conversationReceived)) { | ||||||
|  |                                 conversationList.add(position, conversationReceived); | ||||||
|  |                                 conversationAdapter.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 == conversationList.size() && !conversationList.contains(conversationReceived)) { | ||||||
|  |                         conversationList.add(position, conversationReceived); | ||||||
|  |                         conversationAdapter.notifyItemInserted(position); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
|     public void scrollToTop() { |     public void scrollToTop() { | ||||||
|         binding.recyclerView.scrollToPosition(0); |         binding.recyclerView.scrollToPosition(0); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     @Override | ||||||
|  |     public void onClickMinId(String min_id, Conversation conversationToUpdate) { | ||||||
|  |         min_id_fetch_more = min_id; | ||||||
|  |         route(FragmentMastodonTimeline.DIRECTION.TOP, true, conversationToUpdate); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public void onClickMaxId(String max_id, Conversation conversationToUpdate) { | ||||||
|  |         max_id_fetch_more = max_id; | ||||||
|  |         route(FragmentMastodonTimeline.DIRECTION.BOTTOM, true, conversationToUpdate); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| } | } | ||||||
|  | @ -88,7 +88,6 @@ public class FragmentMastodonNotification extends Fragment implements Notificati | ||||||
|     }; |     }; | ||||||
|     private String max_id, min_id, min_id_fetch_more, max_id_fetch_more; |     private String max_id, min_id, min_id_fetch_more, max_id_fetch_more; | ||||||
|     private LinearLayoutManager mLayoutManager; |     private LinearLayoutManager mLayoutManager; | ||||||
|     private ArrayList<String> idOfAddedNotifications; |  | ||||||
|     private NotificationTypeEnum notificationType; |     private NotificationTypeEnum notificationType; | ||||||
|     private boolean aggregateNotification; |     private boolean aggregateNotification; | ||||||
| 
 | 
 | ||||||
|  | @ -121,7 +120,7 @@ public class FragmentMastodonNotification extends Fragment implements Notificati | ||||||
|         int position = 0; |         int position = 0; | ||||||
|         boolean found = false; |         boolean found = false; | ||||||
|         for (Notification _notification : notificationList) { |         for (Notification _notification : notificationList) { | ||||||
|             if (_notification.status != null && _notification.id.compareTo(notification.id) == 0) { |             if (_notification != null && _notification.id.compareTo(notification.id) == 0) { | ||||||
|                 found = true; |                 found = true; | ||||||
|                 break; |                 break; | ||||||
|             } |             } | ||||||
|  | @ -134,7 +133,6 @@ public class FragmentMastodonNotification extends Fragment implements Notificati | ||||||
|                              ViewGroup container, Bundle savedInstanceState) { |                              ViewGroup container, Bundle savedInstanceState) { | ||||||
| 
 | 
 | ||||||
|         flagLoading = false; |         flagLoading = false; | ||||||
|         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) { | ||||||
|  | @ -142,8 +140,6 @@ public class FragmentMastodonNotification extends Fragment implements Notificati | ||||||
|         } |         } | ||||||
|         aggregateNotification = false; |         aggregateNotification = false; | ||||||
|         binding.getRoot().setBackgroundColor(ThemeHelper.getBackgroundColor(requireActivity())); |         binding.getRoot().setBackgroundColor(ThemeHelper.getBackgroundColor(requireActivity())); | ||||||
|         SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(requireActivity()); |  | ||||||
|         String excludedCategories = sharedpreferences.getString(getString(R.string.SET_EXCLUDED_NOTIFICATIONS_TYPE) + BaseMainActivity.currentUserID + BaseMainActivity.currentInstance, null); |  | ||||||
|         int c1 = getResources().getColor(R.color.cyanea_accent_reference); |         int c1 = getResources().getColor(R.color.cyanea_accent_reference); | ||||||
|         binding.swipeContainer.setProgressBackgroundColorSchemeColor(getResources().getColor(R.color.cyanea_primary_reference)); |         binding.swipeContainer.setProgressBackgroundColorSchemeColor(getResources().getColor(R.color.cyanea_primary_reference)); | ||||||
|         binding.swipeContainer.setColorSchemeColors( |         binding.swipeContainer.setColorSchemeColors( | ||||||
|  | @ -222,9 +218,7 @@ public class FragmentMastodonNotification extends Fragment implements Notificati | ||||||
|             binding.noAction.setVisibility(View.GONE); |             binding.noAction.setVisibility(View.GONE); | ||||||
|             binding.recyclerView.setVisibility(View.VISIBLE); |             binding.recyclerView.setVisibility(View.VISIBLE); | ||||||
|         } |         } | ||||||
|         for (Notification notification : notifications.notifications) { | 
 | ||||||
|             idOfAddedNotifications.add(notification.id); |  | ||||||
|         } |  | ||||||
|         flagLoading = notifications.pagination.max_id == null; |         flagLoading = notifications.pagination.max_id == null; | ||||||
|         if (aggregateNotification) { |         if (aggregateNotification) { | ||||||
|             notifications.notifications = aggregateNotifications(notifications.notifications); |             notifications.notifications = aggregateNotifications(notifications.notifications); | ||||||
|  | @ -455,6 +449,7 @@ public class FragmentMastodonNotification extends Fragment implements Notificati | ||||||
|                         StatusCache statusCache = new StatusCache(); |                         StatusCache statusCache = new StatusCache(); | ||||||
|                         statusCache.instance = BaseMainActivity.currentInstance; |                         statusCache.instance = BaseMainActivity.currentInstance; | ||||||
|                         statusCache.user_id = BaseMainActivity.currentUserID; |                         statusCache.user_id = BaseMainActivity.currentUserID; | ||||||
|  |                         notificationToUpdate.isFetchMore = false; | ||||||
|                         statusCache.notification = notificationToUpdate; |                         statusCache.notification = notificationToUpdate; | ||||||
|                         statusCache.status_id = notificationToUpdate.id; |                         statusCache.status_id = notificationToUpdate.id; | ||||||
|                         try { |                         try { | ||||||
|  |  | ||||||
|  | @ -396,6 +396,7 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. | ||||||
|                         StatusCache statusCache = new StatusCache(); |                         StatusCache statusCache = new StatusCache(); | ||||||
|                         statusCache.instance = BaseMainActivity.currentInstance; |                         statusCache.instance = BaseMainActivity.currentInstance; | ||||||
|                         statusCache.user_id = BaseMainActivity.currentUserID; |                         statusCache.user_id = BaseMainActivity.currentUserID; | ||||||
|  |                         statusToUpdate.isFetchMore = false; | ||||||
|                         statusCache.status = statusToUpdate; |                         statusCache.status = statusToUpdate; | ||||||
|                         statusCache.status_id = statusToUpdate.id; |                         statusCache.status_id = statusToUpdate.id; | ||||||
|                         try { |                         try { | ||||||
|  |  | ||||||
|  | @ -115,6 +115,7 @@ public class NotificationsVM extends AndroidViewModel { | ||||||
|                         notifications.pagination = MastodonHelper.getPagination(notificationsResponse.headers()); |                         notifications.pagination = MastodonHelper.getPagination(notificationsResponse.headers()); | ||||||
| 
 | 
 | ||||||
|                         if (notifications.notifications != null && notifications.notifications.size() > 0) { |                         if (notifications.notifications != null && notifications.notifications.size() > 0) { | ||||||
|  |                             addFetchMoreNotifications(notifications.notifications, notificationList, timelineParams); | ||||||
|                             for (Notification notification : notifications.notifications) { |                             for (Notification notification : notifications.notifications) { | ||||||
|                                 StatusCache statusCacheDAO = new StatusCache(getApplication().getApplicationContext()); |                                 StatusCache statusCacheDAO = new StatusCache(getApplication().getApplicationContext()); | ||||||
|                                 StatusCache statusCache = new StatusCache(); |                                 StatusCache statusCache = new StatusCache(); | ||||||
|  | @ -130,7 +131,6 @@ public class NotificationsVM extends AndroidViewModel { | ||||||
|                                     e.printStackTrace(); |                                     e.printStackTrace(); | ||||||
|                                 } |                                 } | ||||||
|                             } |                             } | ||||||
|                             addFetchMoreNotifications(notifications.notifications, notificationList, timelineParams); |  | ||||||
|                         } |                         } | ||||||
|                     } |                     } | ||||||
|                 } catch (Exception e) { |                 } catch (Exception e) { | ||||||
|  |  | ||||||
|  | @ -363,6 +363,27 @@ public class TimelinesVM extends AndroidViewModel { | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     private static void addFetchMoreConversation(List<Conversation> conversationList, List<Conversation> timelineConversations, TimelineParams timelineParams) throws DBException { | ||||||
|  |         if (conversationList != null && conversationList.size() > 0 && timelineConversations != null && timelineConversations.size() > 0) { | ||||||
|  |             if (timelineParams.direction == FragmentMastodonTimeline.DIRECTION.REFRESH || timelineParams.direction == FragmentMastodonTimeline.DIRECTION.SCROLL_TOP) { | ||||||
|  |                 //When refreshing/scrolling to TOP, if last statuses fetched has a greater id from newest in cache, there is potential hole | ||||||
|  |                 if (conversationList.get(conversationList.size() - 1).id.compareToIgnoreCase(timelineConversations.get(0).id) > 0) { | ||||||
|  |                     conversationList.get(conversationList.size() - 1).isFetchMore = true; | ||||||
|  |                     conversationList.get(conversationList.size() - 1).positionFetchMore = Conversation.PositionFetchMore.TOP; | ||||||
|  |                 } | ||||||
|  |             } else if (timelineParams.direction == FragmentMastodonTimeline.DIRECTION.TOP && timelineParams.fetchingMissing) { | ||||||
|  |                 if (!timelineConversations.contains(conversationList.get(0))) { | ||||||
|  |                     conversationList.get(0).isFetchMore = true; | ||||||
|  |                     conversationList.get(0).positionFetchMore = Conversation.PositionFetchMore.BOTTOM; | ||||||
|  |                 } | ||||||
|  |             } else if (timelineParams.direction == FragmentMastodonTimeline.DIRECTION.BOTTOM && timelineParams.fetchingMissing) { | ||||||
|  |                 if (!timelineConversations.contains(conversationList.get(conversationList.size() - 1))) { | ||||||
|  |                     conversationList.get(conversationList.size() - 1).isFetchMore = true; | ||||||
|  |                     conversationList.get(conversationList.size() - 1).positionFetchMore = Conversation.PositionFetchMore.TOP; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|     public LiveData<Statuses> getTimeline(List<Status> timelineStatuses, TimelineParams timelineParams) { |     public LiveData<Statuses> getTimeline(List<Status> timelineStatuses, TimelineParams timelineParams) { | ||||||
| 
 | 
 | ||||||
|  | @ -397,6 +418,7 @@ public class TimelinesVM extends AndroidViewModel { | ||||||
|                         statuses.statuses = TimelineHelper.filterStatus(getApplication().getApplicationContext(), statusList, timelineParams.type); |                         statuses.statuses = TimelineHelper.filterStatus(getApplication().getApplicationContext(), statusList, timelineParams.type); | ||||||
|                         statuses.pagination = MastodonHelper.getPagination(timelineResponse.headers()); |                         statuses.pagination = MastodonHelper.getPagination(timelineResponse.headers()); | ||||||
|                         if (statusList != null && statusList.size() > 0) { |                         if (statusList != null && statusList.size() > 0) { | ||||||
|  |                             addFetchMore(statusList, timelineStatuses, timelineParams); | ||||||
|                             for (Status status : statuses.statuses) { |                             for (Status status : statuses.statuses) { | ||||||
|                                 StatusCache statusCacheDAO = new StatusCache(getApplication().getApplicationContext()); |                                 StatusCache statusCacheDAO = new StatusCache(getApplication().getApplicationContext()); | ||||||
|                                 StatusCache statusCache = new StatusCache(); |                                 StatusCache statusCache = new StatusCache(); | ||||||
|  | @ -411,7 +433,6 @@ public class TimelinesVM extends AndroidViewModel { | ||||||
|                                     e.printStackTrace(); |                                     e.printStackTrace(); | ||||||
|                                 } |                                 } | ||||||
|                             } |                             } | ||||||
|                             addFetchMore(statusList, timelineStatuses, timelineParams); |  | ||||||
|                         } |                         } | ||||||
|                     } |                     } | ||||||
|                 } catch (Exception e) { |                 } catch (Exception e) { | ||||||
|  | @ -476,28 +497,46 @@ public class TimelinesVM extends AndroidViewModel { | ||||||
|         return statusDraftListMutableLiveData; |         return statusDraftListMutableLiveData; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
|     /** |     /** | ||||||
|      * Show conversations |      * Show conversations | ||||||
|      * |      * | ||||||
|      * @return {@link LiveData} containing a {@link Conversations} |      * @return {@link LiveData} containing a {@link Conversations} | ||||||
|      */ |      */ | ||||||
|     public LiveData<Conversations> getConversations(@NonNull String instance, String token, |     public LiveData<Conversations> getConversations(List<Conversation> conversationsTimeline, TimelineParams timelineParams) { | ||||||
|                                                     String maxId, |  | ||||||
|                                                     String sinceId, |  | ||||||
|                                                     String minId, |  | ||||||
|                                                     int limit) { |  | ||||||
|         conversationListMutableLiveData = new MutableLiveData<>(); |         conversationListMutableLiveData = new MutableLiveData<>(); | ||||||
|         MastodonTimelinesService mastodonTimelinesService = init(instance); |         MastodonTimelinesService mastodonTimelinesService = init(timelineParams.instance); | ||||||
|         new Thread(() -> { |         new Thread(() -> { | ||||||
|             Conversations conversations = null; |             Conversations conversations = null; | ||||||
|             Call<List<Conversation>> conversationsCall = mastodonTimelinesService.getConversations(token, maxId, sinceId, minId, limit); |             Call<List<Conversation>> conversationsCall = mastodonTimelinesService.getConversations(timelineParams.token, timelineParams.maxId, timelineParams.sinceId, timelineParams.minId, timelineParams.limit); | ||||||
|             if (conversationsCall != null) { |             if (conversationsCall != null) { | ||||||
|                 conversations = new Conversations(); |                 conversations = new Conversations(); | ||||||
|                 try { |                 try { | ||||||
|                     Response<List<Conversation>> conversationsResponse = conversationsCall.execute(); |                     Response<List<Conversation>> conversationsResponse = conversationsCall.execute(); | ||||||
|                     if (conversationsResponse.isSuccessful()) { |                     if (conversationsResponse.isSuccessful()) { | ||||||
|                         conversations.conversations = conversationsResponse.body(); |                         List<Conversation> conversationList = conversationsResponse.body(); | ||||||
|  |                         conversations.conversations = conversationList; | ||||||
|                         conversations.pagination = MastodonHelper.getPagination(conversationsResponse.headers()); |                         conversations.pagination = MastodonHelper.getPagination(conversationsResponse.headers()); | ||||||
|  | 
 | ||||||
|  |                         if (conversationList != null && conversationList.size() > 0) { | ||||||
|  | 
 | ||||||
|  |                             addFetchMoreConversation(conversationList, conversationsTimeline, timelineParams); | ||||||
|  | 
 | ||||||
|  |                             for (Conversation conversation : conversations.conversations) { | ||||||
|  |                                 StatusCache statusCacheDAO = new StatusCache(getApplication().getApplicationContext()); | ||||||
|  |                                 StatusCache statusCache = new StatusCache(); | ||||||
|  |                                 statusCache.instance = timelineParams.instance; | ||||||
|  |                                 statusCache.user_id = timelineParams.userId; | ||||||
|  |                                 statusCache.conversation = conversation; | ||||||
|  |                                 statusCache.type = Timeline.TimeLineEnum.CONVERSATION; | ||||||
|  |                                 statusCache.status_id = conversation.id; | ||||||
|  |                                 try { | ||||||
|  |                                     statusCacheDAO.insertOrUpdate(statusCache, timelineParams.slug); | ||||||
|  |                                 } catch (DBException e) { | ||||||
|  |                                     e.printStackTrace(); | ||||||
|  |                                 } | ||||||
|  |                             } | ||||||
|  |                         } | ||||||
|                     } |                     } | ||||||
|                 } catch (Exception e) { |                 } catch (Exception e) { | ||||||
|                     e.printStackTrace(); |                     e.printStackTrace(); | ||||||
|  | @ -512,6 +551,36 @@ public class TimelinesVM extends AndroidViewModel { | ||||||
|         return conversationListMutableLiveData; |         return conversationListMutableLiveData; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
|  |     public LiveData<Conversations> getConversationsCache(List<Conversation> timelineConversations, TimelineParams timelineParams) { | ||||||
|  |         conversationListMutableLiveData = new MutableLiveData<>(); | ||||||
|  |         new Thread(() -> { | ||||||
|  |             StatusCache statusCacheDAO = new StatusCache(getApplication().getApplicationContext()); | ||||||
|  |             Conversations conversations = null; | ||||||
|  |             try { | ||||||
|  |                 conversations = statusCacheDAO.getConversations(timelineParams.instance, timelineParams.userId, timelineParams.maxId, timelineParams.minId, timelineParams.sinceId); | ||||||
|  |                 if (conversations != null) { | ||||||
|  |                     if (conversations.conversations != null && conversations.conversations.size() > 0) { | ||||||
|  |                         for (Conversation conversation : conversations.conversations) { | ||||||
|  |                             conversation.cached = true; | ||||||
|  |                         } | ||||||
|  |                         addFetchMoreConversation(conversations.conversations, timelineConversations, timelineParams); | ||||||
|  |                         conversations.pagination = new Pagination(); | ||||||
|  |                         conversations.pagination.min_id = conversations.conversations.get(0).id; | ||||||
|  |                         conversations.pagination.max_id = conversations.conversations.get(conversations.conversations.size() - 1).id; | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } catch (DBException e) { | ||||||
|  |                 e.printStackTrace(); | ||||||
|  |             } | ||||||
|  |             Handler mainHandler = new Handler(Looper.getMainLooper()); | ||||||
|  |             Conversations finalConversations = conversations; | ||||||
|  |             Runnable myRunnable = () -> conversationListMutableLiveData.setValue(finalConversations); | ||||||
|  |             mainHandler.post(myRunnable); | ||||||
|  |         }).start(); | ||||||
|  |         return conversationListMutableLiveData; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     /** |     /** | ||||||
|      * Remove conversation |      * Remove conversation | ||||||
|      * |      * | ||||||
|  |  | ||||||
|  | @ -32,8 +32,20 @@ | ||||||
|         <androidx.appcompat.widget.LinearLayoutCompat |         <androidx.appcompat.widget.LinearLayoutCompat | ||||||
|             android:layout_width="match_parent" |             android:layout_width="match_parent" | ||||||
|             android:layout_height="wrap_content" |             android:layout_height="wrap_content" | ||||||
|  |             android:orientation="horizontal" | ||||||
|             android:padding="6dp"> |             android:padding="6dp"> | ||||||
| 
 | 
 | ||||||
|  |             <androidx.appcompat.widget.AppCompatImageView | ||||||
|  |                 android:id="@+id/cache_indicator" | ||||||
|  |                 android:layout_width="16sp" | ||||||
|  |                 android:layout_height="16sp" | ||||||
|  |                 android:layout_gravity="center" | ||||||
|  |                 android:layout_marginEnd="12dp" | ||||||
|  |                 android:adjustViewBounds="true" | ||||||
|  |                 android:src="@drawable/ic_baseline_cached_24" | ||||||
|  |                 android:visibility="gone" | ||||||
|  |                 tools:visibility="visible" /> | ||||||
|  | 
 | ||||||
|             <HorizontalScrollView |             <HorizontalScrollView | ||||||
|                 android:layout_width="0dp" |                 android:layout_width="0dp" | ||||||
|                 android:layout_height="match_parent" |                 android:layout_height="match_parent" | ||||||
|  | @ -104,6 +116,12 @@ | ||||||
|                 tools:ignore="RtlSymmetry" /> |                 tools:ignore="RtlSymmetry" /> | ||||||
| 
 | 
 | ||||||
|         </HorizontalScrollView> |         </HorizontalScrollView> | ||||||
|  | 
 | ||||||
|  |         <include | ||||||
|  |             android:id="@+id/layout_fetch_more" | ||||||
|  |             layout="@layout/drawer_fetch_more" | ||||||
|  |             android:visibility="gone" | ||||||
|  |             tools:visibility="visible" /> | ||||||
|     </androidx.appcompat.widget.LinearLayoutCompat> |     </androidx.appcompat.widget.LinearLayoutCompat> | ||||||
| 
 | 
 | ||||||
| </com.google.android.material.card.MaterialCardView> | </com.google.android.material.card.MaterialCardView> | ||||||
|  |  | ||||||
		Loading…
	
		Reference in a new issue