From 03bf06b6a03808739e24d25e31a5893c05190e61 Mon Sep 17 00:00:00 2001 From: Thomas Date: Sat, 24 Sep 2022 15:27:57 +0200 Subject: [PATCH] improve cache --- .../client/entities/app/QuickLoad.java | 4 +- .../client/entities/app/StatusCache.java | 184 +++++++++-- .../app/fedilab/android/sqlite/Sqlite.java | 8 +- .../timeline/FragmentMastodonTimeline.java | 297 +++++------------- .../viewmodel/mastodon/TimelinesVM.java | 270 ++++++---------- app/src/main/res/values/strings.xml | 2 + 6 files changed, 343 insertions(+), 422 deletions(-) diff --git a/app/src/main/java/app/fedilab/android/client/entities/app/QuickLoad.java b/app/src/main/java/app/fedilab/android/client/entities/app/QuickLoad.java index 26140deb..64136484 100644 --- a/app/src/main/java/app/fedilab/android/client/entities/app/QuickLoad.java +++ b/app/src/main/java/app/fedilab/android/client/entities/app/QuickLoad.java @@ -76,8 +76,8 @@ public class QuickLoad { * @param timeLineType - Timeline.TimeLineEnum * @return boolean */ - private static boolean cannotBeStored(Timeline.TimeLineEnum timeLineType) { - return timeLineType != Timeline.TimeLineEnum.HOME && timeLineType != Timeline.TimeLineEnum.LOCAL && timeLineType != Timeline.TimeLineEnum.PUBLIC && timeLineType != Timeline.TimeLineEnum.REMOTE && timeLineType != Timeline.TimeLineEnum.LIST && timeLineType != Timeline.TimeLineEnum.TAG; + public static boolean cannotBeStored(Timeline.TimeLineEnum timeLineType) { + return timeLineType != Timeline.TimeLineEnum.LOCAL && timeLineType != Timeline.TimeLineEnum.PUBLIC && timeLineType != Timeline.TimeLineEnum.REMOTE && timeLineType != Timeline.TimeLineEnum.LIST && timeLineType != Timeline.TimeLineEnum.TAG; } /** diff --git a/app/src/main/java/app/fedilab/android/client/entities/app/StatusCache.java b/app/src/main/java/app/fedilab/android/client/entities/app/StatusCache.java index 63f21547..4f83bf3c 100644 --- a/app/src/main/java/app/fedilab/android/client/entities/app/StatusCache.java +++ b/app/src/main/java/app/fedilab/android/client/entities/app/StatusCache.java @@ -27,6 +27,7 @@ import java.util.Collections; import java.util.Date; import java.util.List; +import app.fedilab.android.activities.MainActivity; import app.fedilab.android.client.entities.api.Pagination; import app.fedilab.android.client.entities.api.Status; import app.fedilab.android.client.entities.api.Statuses; @@ -44,8 +45,10 @@ public class StatusCache { public String user_id; @SerializedName("instance") public String instance; + @SerializedName("slug") + public String slug; @SerializedName("type") - public CacheEnum type; + public Timeline.TimeLineEnum type; @SerializedName("status_id") public String status_id; @SerializedName("status") @@ -104,16 +107,17 @@ public class StatusCache { * @return long - db id * @throws DBException exception with database */ - public long insertOrUpdate(StatusCache statusCache) throws DBException { + public long insertOrUpdate(StatusCache statusCache, String slug) throws DBException { if (db == null) { throw new DBException("db is null. Wrong initialization."); } + statusCache.slug = slug; boolean exists = statusExist(statusCache); long idReturned; if (exists) { idReturned = updateStatus(statusCache); } else { - idReturned = insertStatus(statusCache); + idReturned = insertStatus(statusCache, slug); } return idReturned; } @@ -155,6 +159,27 @@ public class StatusCache { return (count > 0); } + /** + * Check if a status exists in db + * + * @param status Status {@link Status} + * @return boolean - StatusCache exists + * @throws DBException Exception + */ + public boolean statusExist(Status status) throws DBException { + if (db == null) { + throw new DBException("db is null. Wrong initialization."); + } + Cursor mCount = db.rawQuery("select count(*) from " + Sqlite.TABLE_STATUS_CACHE + + " where " + Sqlite.COL_STATUS_ID + " = '" + status.id + "'" + + " AND " + Sqlite.COL_INSTANCE + " = '" + MainActivity.currentInstance + "'" + + " AND " + Sqlite.COL_USER_ID + "= '" + MainActivity.currentUserID + "'", null); + mCount.moveToFirst(); + int count = mCount.getInt(0); + mCount.close(); + return (count > 0); + } + /** * Insert a status in db * @@ -162,14 +187,14 @@ public class StatusCache { * @return long - db id * @throws DBException exception with database */ - private long insertStatus(StatusCache statusCache) throws DBException { + private long insertStatus(StatusCache statusCache, String slug) throws DBException { if (db == null) { throw new DBException("db is null. Wrong initialization."); } ContentValues values = new ContentValues(); values.put(Sqlite.COL_USER_ID, statusCache.user_id); values.put(Sqlite.COL_INSTANCE, statusCache.instance); - values.put(Sqlite.COL_TYPE, statusCache.type.getValue()); + values.put(Sqlite.COL_SLUG, statusCache.slug); values.put(Sqlite.COL_STATUS_ID, statusCache.status_id); values.put(Sqlite.COL_STATUS, mastodonStatusToStringStorage(statusCache.status)); values.put(Sqlite.COL_CREATED_AT, Helper.dateToString(new Date())); @@ -195,9 +220,6 @@ public class StatusCache { } ContentValues values = new ContentValues(); values.put(Sqlite.COL_USER_ID, statusCache.user_id); - if (statusCache.type != null) { - values.put(Sqlite.COL_TYPE, statusCache.type.getValue()); - } values.put(Sqlite.COL_STATUS_ID, statusCache.status_id); values.put(Sqlite.COL_STATUS, mastodonStatusToStringStorage(statusCache.status)); values.put(Sqlite.COL_UPDATED_AT, Helper.dateToString(new Date())); @@ -274,10 +296,63 @@ public class StatusCache { } } + + /** + * Get newest status for a timeline + * + * @param slug String - slug for the timeline (it's a unique string value for a timeline) + * @param instance String - instance + * @param user_id String - us + * @return Statuses + * @throws DBException - throws a db exception + */ + public Status getNewestStatus(String slug, String instance, String user_id) throws DBException { + if (db == null) { + throw new DBException("db is null. Wrong initialization."); + } + String selection = Sqlite.COL_INSTANCE + "='" + instance + "' AND " + Sqlite.COL_USER_ID + "= '" + user_id + "' AND " + Sqlite.COL_SLUG + "= '" + slug + "'"; + try { + Cursor c = db.query(Sqlite.TABLE_STATUS_CACHE, null, selection, null, null, null, Sqlite.COL_STATUS_ID + " DESC", "1"); + Statuses statuses = createStatusReply(cursorToListOfStatuses(c)); + if (statuses.statuses != null && statuses.statuses.size() > 0) { + return statuses.statuses.get(0); + } else return null; + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + /** + * Get oldest status for a timeline + * + * @param slug String - slug for the timeline (it's a unique string value for a timeline) + * @param instance String - instance + * @param user_id String - us + * @return Statuses + * @throws DBException - throws a db exception + */ + public Status getOldestStatus(String slug, String instance, String user_id) throws DBException { + if (db == null) { + throw new DBException("db is null. Wrong initialization."); + } + String selection = Sqlite.COL_INSTANCE + "='" + instance + "' AND " + Sqlite.COL_USER_ID + "= '" + user_id + "' AND " + Sqlite.COL_SLUG + "= '" + slug + "'"; + try { + Cursor c = db.query(Sqlite.TABLE_STATUS_CACHE, null, selection, null, null, null, Sqlite.COL_STATUS_ID + " ASC", "1"); + Statuses statuses = createStatusReply(cursorToListOfStatuses(c)); + if (statuses.statuses != null && statuses.statuses.size() > 0) { + return statuses.statuses.get(0); + } else return null; + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + /** * Get paginated statuses from db * - * @param type CacheEnum - not used yet but will allow to extend cache to other timelines + * @param slug String - slug for the timeline (it's a unique string value for a timeline) * @param instance String - instance * @param user_id String - us * @param max_id String - status having max id @@ -285,12 +360,12 @@ public class StatusCache { * @return Statuses * @throws DBException - throws a db exception */ - public Statuses geStatuses(CacheEnum type, String instance, String user_id, String max_id, String min_id, String since_id) throws DBException { + public Statuses geStatuses(String slug, 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 + "'"; + String selection = Sqlite.COL_INSTANCE + "='" + instance + "' AND " + Sqlite.COL_USER_ID + "= '" + user_id + "' AND " + Sqlite.COL_SLUG + "= '" + slug + "'"; String limit = String.valueOf(MastodonHelper.statusesPerCall(context)); if (min_id != null) { selection += "AND " + Sqlite.COL_STATUS_ID + " > '" + min_id + "'"; @@ -310,16 +385,70 @@ public class StatusCache { } } + /** + * Get statuses from db + * + * @return Statuses + * @throws DBException - throws a db exception + */ + public Status getTopFetchMore(String slug, String instance, String user_id, String status_id) throws DBException { + if (db == null) { + throw new DBException("db is null. Wrong initialization."); + } + String selection = Sqlite.COL_INSTANCE + "='" + instance + "' AND " + + Sqlite.COL_USER_ID + "= '" + user_id + "' AND " + + Sqlite.COL_SLUG + "= '" + slug + "' AND " + + Sqlite.COL_STATUS_ID + " > '" + status_id + "'"; + try { + Cursor c = db.query(Sqlite.TABLE_STATUS_CACHE, null, selection, null, null, null, Sqlite.COL_STATUS_ID + " ASC", "1"); + if (c != null && c.getCount() > 0) { + return convertCursorToStatus(c); + } else { + return null; + } + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } /** - * @param type CacheEnum - not used yet but will allow to extend cache to other timelines + * Get statuses from db + * + * @param statusCache StatusCache - status in cache to compare + * @return Statuses + * @throws DBException - throws a db exception + */ + public Status getBottomFetchMore(String slug, String instance, String user_id, String status_id) throws DBException { + if (db == null) { + throw new DBException("db is null. Wrong initialization."); + } + String selection = Sqlite.COL_INSTANCE + "='" + instance + "' AND " + + Sqlite.COL_USER_ID + "= '" + user_id + "' AND " + + Sqlite.COL_SLUG + "= '" + slug + "' AND " + + Sqlite.COL_STATUS_ID + " < '" + status_id + "'"; + try { + Cursor c = db.query(Sqlite.TABLE_STATUS_CACHE, null, selection, null, null, null, Sqlite.COL_STATUS_ID + " DESC", "1"); + if (c != null && c.getCount() > 0) { + return convertCursorToStatus(c); + } else { + return null; + } + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + /** + * @param slug String - slug for the timeline (it's a unique string value for a timeline) * @param instance String - instance * @param user_id String - us * @param search String search * @return - List * @throws DBException exception */ - public List searchStatus(CacheEnum type, String instance, String user_id, String search) throws DBException { + public List searchStatus(String slug, String instance, String user_id, String search) throws DBException { if (db == null) { throw new DBException("db is null. Wrong initialization."); } @@ -343,6 +472,22 @@ public class StatusCache { return reply; } + public enum order { + @SerializedName("ASC") + ASC("ASC"), + @SerializedName("DESC") + DESC("DESC"); + private final String value; + + order(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + } + public int count(BaseAccount account) throws DBException { if (db == null) { @@ -412,17 +557,4 @@ public class StatusCache { return restoreStatusFromString(serializedStatus); } - public enum CacheEnum { - @SerializedName("HOME") - HOME("HOME"); - private final String value; - - CacheEnum(String value) { - this.value = value; - } - - public String getValue() { - return value; - } - } } diff --git a/app/src/main/java/app/fedilab/android/sqlite/Sqlite.java b/app/src/main/java/app/fedilab/android/sqlite/Sqlite.java index b4e771d9..6f986b54 100644 --- a/app/src/main/java/app/fedilab/android/sqlite/Sqlite.java +++ b/app/src/main/java/app/fedilab/android/sqlite/Sqlite.java @@ -23,7 +23,7 @@ import android.database.sqlite.SQLiteOpenHelper; public class Sqlite extends SQLiteOpenHelper { - public static final int DB_VERSION = 5; + public static final int DB_VERSION = 6; public static final String DB_NAME = "fedilab_db"; //Table of owned accounts @@ -111,6 +111,7 @@ public class Sqlite extends SQLiteOpenHelper { + COL_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " + COL_USER_ID + " TEXT NOT NULL, " + COL_INSTANCE + " TEXT NOT NULL, " + COL_TYPE + " TEXT NOT NULL, " + + COL_SLUG + " TEXT, " + COL_STATUS_ID + " TEXT NOT NULL, " + COL_STATUS + " TEXT NOT NULL, " + COL_CREATED_AT + " TEXT NOT NULL," @@ -202,7 +203,6 @@ public class Sqlite extends SQLiteOpenHelper { db.execSQL(CREATE_TABLE_STATUS_DRAFT); db.execSQL(CREATE_TABLE_PINNED_TIMELINES); db.execSQL(CREATE_TABLE_SCHEDULE_BOOST); - db.execSQL(CREATE_TABLE_QUICK_LOAD); db.execSQL(CREATE_TABLE_BOTTOM_MENU); db.execSQL(CREATE_DOMAINS_TRACKING); } @@ -223,6 +223,10 @@ public class Sqlite extends SQLiteOpenHelper { db.execSQL("ALTER TABLE " + TABLE_USER_ACCOUNT + " ADD COLUMN " + COL_ADMIN + " INTEGER NOT NULL DEFAULT 0"); case 4: db.execSQL(CREATE_DOMAINS_TRACKING); + case 5: + db.execSQL("ALTER TABLE " + TABLE_STATUS_CACHE + " ADD COLUMN " + COL_SLUG + " TEXT"); + db.execSQL("DELETE FROM " + TABLE_STATUS_CACHE); + db.execSQL("DROP TABLE IF EXISTS " + TABLE_QUICK_LOAD); default: break; } diff --git a/app/src/main/java/app/fedilab/android/ui/fragment/timeline/FragmentMastodonTimeline.java b/app/src/main/java/app/fedilab/android/ui/fragment/timeline/FragmentMastodonTimeline.java index 0d4ed312..f94d0373 100644 --- a/app/src/main/java/app/fedilab/android/ui/fragment/timeline/FragmentMastodonTimeline.java +++ b/app/src/main/java/app/fedilab/android/ui/fragment/timeline/FragmentMastodonTimeline.java @@ -44,7 +44,6 @@ import app.fedilab.android.BaseMainActivity; import app.fedilab.android.R; import app.fedilab.android.client.entities.api.Account; import app.fedilab.android.client.entities.api.Attachment; -import app.fedilab.android.client.entities.api.Marker; import app.fedilab.android.client.entities.api.Pagination; import app.fedilab.android.client.entities.api.Status; import app.fedilab.android.client.entities.api.Statuses; @@ -127,7 +126,6 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. } } }; - private List markers; private String list_id; private TagTimeline tagTimeline; private LinearLayoutManager mLayoutManager; @@ -251,8 +249,6 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. binding.loader.setVisibility(View.VISIBLE); binding.recyclerView.setVisibility(View.GONE); - //Markers for home and notifications to get last read ones - markers = new ArrayList<>(); max_id = statusReport != null ? statusReport.id : null; flagLoading = false; router(null); @@ -557,7 +553,12 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. if (statuses != null && statuses.size() > position) { try { Status status = statuses.get(position); + SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(requireActivity()); + SharedPreferences.Editor editor = sharedpreferences.edit(); + editor.putString(getString(R.string.SET_HOME_INNER_MARKER) + BaseMainActivity.currentUserID + BaseMainActivity.currentInstance, status.id); + editor.apply(); timelinesVM.addMarker(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, status.id, null); + } catch (Exception ignored) { } } @@ -579,6 +580,66 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. } } + /** + * Router for timelines + * + * @param direction - DIRECTION null if first call, then is set to TOP or BOTTOM depending of scroll + */ + private void routeCommon(DIRECTION direction, boolean fetchingMissing) { + if (binding == null || getActivity() == null || !isAdded()) { + return; + } + //Initialize with default params + TimelinesVM.TimelineParams timelineParams = new TimelinesVM.TimelineParams(timelineType, direction, ident); + timelineParams.limit = MastodonHelper.statusesPerCall(requireActivity()); + timelineParams.maxId = fetchingMissing ? max_id_fetch_more : max_id; + timelineParams.minId = fetchingMissing ? min_id_fetch_more : min_id; + timelineParams.fetchingMissing = fetchingMissing; + switch (timelineType) { + case LOCAL: + timelineParams.local = true; + timelineParams.remote = false; + break; + case PUBLIC: + timelineParams.local = false; + timelineParams.remote = true; + break; + case LIST: + timelineParams.listId = list_id; + break; + case TAG: + if (tagTimeline == null) { + tagTimeline = new TagTimeline(); + tagTimeline.name = search; + } + timelineParams.onlyMedia = timelineType == Timeline.TimeLineEnum.ART; + timelineParams.none = tagTimeline.none; + timelineParams.all = tagTimeline.all; + timelineParams.any = tagTimeline.any; + timelineParams.hashtagTrim = tagTimeline.name; + break; + } + if (direction == null) { + timelinesVM.getTimeline(timelineParams) + .observe(getViewLifecycleOwner(), this::initializeStatusesCommonView); + } else if (direction == DIRECTION.BOTTOM) { + timelinesVM.getTimeline(timelineParams) + .observe(getViewLifecycleOwner(), statusesBottom -> dealWithPagination(statusesBottom, DIRECTION.BOTTOM, fetchingMissing)); + } else if (direction == DIRECTION.TOP) { + timelinesVM.getTimeline(timelineParams) + .observe(getViewLifecycleOwner(), statusesBottom -> dealWithPagination(statusesBottom, DIRECTION.TOP, fetchingMissing)); + } else if (direction == DIRECTION.REFRESH || direction == DIRECTION.SCROLL_TOP) { + timelinesVM.getTimeline(timelineParams) + .observe(getViewLifecycleOwner(), statusesRefresh -> { + if (statusAdapter != null) { + dealWithPagination(statusesRefresh, direction, true); + } else { + initializeStatusesCommonView(statusesRefresh); + } + }); + } + } + /** * Router for timelines * @@ -589,17 +650,26 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. if (binding == null || getActivity() == null || !isAdded()) { return; } - boolean nitterInstance = timelineType == Timeline.TimeLineEnum.REMOTE && pinnedTimeline != null && pinnedTimeline.remoteInstance.type == RemoteInstance.InstanceType.NITTER; - QuickLoad quickLoad = new QuickLoad(requireActivity()).getSavedValue(BaseMainActivity.currentUserID, BaseMainActivity.currentInstance, timelineType, ident); + boolean nitterInstance = false; + //For remote instance, we check if it's a Nitter timeline + if (timelineType == Timeline.TimeLineEnum.REMOTE) { + nitterInstance = pinnedTimeline != null && pinnedTimeline.remoteInstance.type == RemoteInstance.InstanceType.NITTER; + } + QuickLoad quickLoad = null; + //For timelines that can be stored we check if there is a stored TL in db + //HOME TL, is excluded and should use its own cache + if (!QuickLoad.cannotBeStored(timelineType)) { + quickLoad = new QuickLoad(requireActivity()).getSavedValue(BaseMainActivity.currentUserID, BaseMainActivity.currentInstance, timelineType, ident); + } if (!nitterInstance && !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 = () -> initializeStatusesCommonView(statuses, quickLoad.position); + QuickLoad finalQuickLoad = quickLoad; + Runnable myRunnable = () -> initializeStatusesCommonView(statuses, finalQuickLoad.position); mainHandler.post(myRunnable); } else { Handler mainHandler = new Handler(Looper.getMainLooper()); @@ -610,49 +680,12 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. // --- HOME TIMELINE --- if (timelineType == Timeline.TimeLineEnum.HOME) { //for more visibility it's done through loadHomeStrategy method - loadHomeStrategy(direction, fetchingMissing); + routeCommon(direction, fetchingMissing); } else if (timelineType == Timeline.TimeLineEnum.LOCAL) { //LOCAL TIMELINE - if (direction == null) { - timelinesVM.getPublic(BaseMainActivity.currentToken, BaseMainActivity.currentInstance, true, false, false, null, null, null, MastodonHelper.statusesPerCall(requireActivity())) - .observe(getViewLifecycleOwner(), this::initializeStatusesCommonView); - } else if (direction == DIRECTION.BOTTOM) { - timelinesVM.getPublic(BaseMainActivity.currentToken, BaseMainActivity.currentInstance, true, false, false, fetchingMissing ? max_id_fetch_more : max_id, null, null, MastodonHelper.statusesPerCall(requireActivity())) - .observe(getViewLifecycleOwner(), statusesBottom -> dealWithPagination(statusesBottom, DIRECTION.BOTTOM, fetchingMissing)); - } else if (direction == DIRECTION.TOP) { - timelinesVM.getPublic(BaseMainActivity.currentToken, BaseMainActivity.currentInstance, true, false, false, null, null, fetchingMissing ? min_id_fetch_more : min_id, MastodonHelper.statusesPerCall(requireActivity())) - .observe(getViewLifecycleOwner(), statusesBottom -> dealWithPagination(statusesBottom, DIRECTION.TOP, fetchingMissing)); - } else if (direction == DIRECTION.REFRESH || direction == DIRECTION.SCROLL_TOP) { - timelinesVM.getPublic(BaseMainActivity.currentToken, BaseMainActivity.currentInstance, true, false, false, null, null, null, MastodonHelper.statusesPerCall(requireActivity())) - .observe(getViewLifecycleOwner(), statusesRefresh -> { - if (statusAdapter != null) { - dealWithPagination(statusesRefresh, direction, true); - } else { - initializeStatusesCommonView(statusesRefresh); - } - }); - } + routeCommon(direction, fetchingMissing); } else if (timelineType == Timeline.TimeLineEnum.PUBLIC) { //PUBLIC TIMELINE - if (direction == null) { - timelinesVM.getPublic(BaseMainActivity.currentToken, BaseMainActivity.currentInstance, false, true, false, null, null, null, MastodonHelper.statusesPerCall(requireActivity())) - .observe(getViewLifecycleOwner(), this::initializeStatusesCommonView); - } else if (direction == DIRECTION.BOTTOM) { - timelinesVM.getPublic(BaseMainActivity.currentToken, BaseMainActivity.currentInstance, false, true, false, fetchingMissing ? max_id_fetch_more : max_id, null, null, MastodonHelper.statusesPerCall(requireActivity())) - .observe(getViewLifecycleOwner(), statusesBottom -> dealWithPagination(statusesBottom, DIRECTION.BOTTOM, fetchingMissing)); - } else if (direction == DIRECTION.TOP) { - timelinesVM.getPublic(BaseMainActivity.currentToken, BaseMainActivity.currentInstance, false, true, false, null, null, fetchingMissing ? min_id_fetch_more : min_id, MastodonHelper.statusesPerCall(requireActivity())) - .observe(getViewLifecycleOwner(), statusesBottom -> dealWithPagination(statusesBottom, DIRECTION.TOP, fetchingMissing)); - } else if (direction == DIRECTION.REFRESH || direction == DIRECTION.SCROLL_TOP) { - timelinesVM.getPublic(BaseMainActivity.currentToken, BaseMainActivity.currentInstance, false, true, false, null, null, null, MastodonHelper.statusesPerCall(requireActivity())) - .observe(getViewLifecycleOwner(), statusesRefresh -> { - if (statusAdapter != null) { - dealWithPagination(statusesRefresh, direction, true); - } else { - initializeStatusesCommonView(statusesRefresh); - } - }); - } + routeCommon(direction, fetchingMissing); } else if (timelineType == Timeline.TimeLineEnum.REMOTE) { //REMOTE TIMELINE - //NITTER TIMELINES if (pinnedTimeline != null && pinnedTimeline.remoteInstance.type == RemoteInstance.InstanceType.NITTER) { if (direction == null) { @@ -718,70 +751,12 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. }); } } else { //Other remote timelines - if (direction == null) { - timelinesVM.getPublic(null, remoteInstance, true, false, false, null, null, null, MastodonHelper.statusesPerCall(requireActivity())) - .observe(getViewLifecycleOwner(), this::initializeStatusesCommonView); - } else if (direction == DIRECTION.BOTTOM) { - timelinesVM.getPublic(null, remoteInstance, true, false, false, fetchingMissing ? max_id_fetch_more : max_id, null, null, MastodonHelper.statusesPerCall(requireActivity())) - .observe(getViewLifecycleOwner(), statusesBottom -> dealWithPagination(statusesBottom, DIRECTION.BOTTOM, fetchingMissing)); - } else if (direction == DIRECTION.TOP) { - timelinesVM.getPublic(null, remoteInstance, true, false, false, null, null, fetchingMissing ? min_id_fetch_more : min_id, MastodonHelper.statusesPerCall(requireActivity())) - .observe(getViewLifecycleOwner(), statusesBottom -> dealWithPagination(statusesBottom, DIRECTION.TOP, fetchingMissing)); - } else if (direction == DIRECTION.REFRESH || direction == DIRECTION.SCROLL_TOP) { - timelinesVM.getPublic(null, remoteInstance, true, false, false, null, null, null, MastodonHelper.statusesPerCall(requireActivity())) - .observe(getViewLifecycleOwner(), statusesRefresh -> { - if (statusAdapter != null) { - dealWithPagination(statusesRefresh, direction, true); - } else { - initializeStatusesCommonView(statusesRefresh); - } - }); - } + routeCommon(direction, fetchingMissing); } } else if (timelineType == Timeline.TimeLineEnum.LIST) { //LIST TIMELINE - if (direction == null) { - timelinesVM.getList(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, list_id, null, null, null, MastodonHelper.statusesPerCall(requireActivity())) - .observe(getViewLifecycleOwner(), this::initializeStatusesCommonView); - } else if (direction == DIRECTION.BOTTOM) { - timelinesVM.getList(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, list_id, fetchingMissing ? max_id_fetch_more : max_id, null, null, MastodonHelper.statusesPerCall(requireActivity())) - .observe(getViewLifecycleOwner(), statusesBottom -> dealWithPagination(statusesBottom, DIRECTION.BOTTOM, fetchingMissing)); - } else if (direction == DIRECTION.TOP) { - timelinesVM.getList(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, list_id, null, null, fetchingMissing ? min_id_fetch_more : min_id, MastodonHelper.statusesPerCall(requireActivity())) - .observe(getViewLifecycleOwner(), statusesBottom -> dealWithPagination(statusesBottom, DIRECTION.TOP, fetchingMissing)); - } else if (direction == DIRECTION.REFRESH || direction == DIRECTION.SCROLL_TOP) { - timelinesVM.getList(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, list_id, null, null, null, MastodonHelper.statusesPerCall(requireActivity())) - .observe(getViewLifecycleOwner(), statusesRefresh -> { - if (statusAdapter != null) { - dealWithPagination(statusesRefresh, direction, true); - } else { - initializeStatusesCommonView(statusesRefresh); - } - }); - } + routeCommon(direction, fetchingMissing); } else if (timelineType == Timeline.TimeLineEnum.TAG || timelineType == Timeline.TimeLineEnum.ART) { //TAG TIMELINE - if (tagTimeline == null) { - tagTimeline = new TagTimeline(); - tagTimeline.name = search; - } - if (direction == null) { - timelinesVM.getHashTag(BaseMainActivity.currentToken, BaseMainActivity.currentInstance, tagTimeline.name, false, tagTimeline.isART, tagTimeline.all, tagTimeline.any, tagTimeline.none, null, null, null, MastodonHelper.statusesPerCall(requireActivity())) - .observe(getViewLifecycleOwner(), this::initializeStatusesCommonView); - } else if (direction == DIRECTION.BOTTOM) { - timelinesVM.getHashTag(BaseMainActivity.currentToken, BaseMainActivity.currentInstance, tagTimeline.name, false, tagTimeline.isART, tagTimeline.all, tagTimeline.any, tagTimeline.none, fetchingMissing ? max_id_fetch_more : max_id, null, null, MastodonHelper.statusesPerCall(requireActivity())) - .observe(getViewLifecycleOwner(), statusesBottom -> dealWithPagination(statusesBottom, DIRECTION.BOTTOM, fetchingMissing)); - } else if (direction == DIRECTION.TOP) { - timelinesVM.getHashTag(BaseMainActivity.currentToken, BaseMainActivity.currentInstance, tagTimeline.name, false, tagTimeline.isART, tagTimeline.all, tagTimeline.any, tagTimeline.none, null, null, fetchingMissing ? min_id_fetch_more : min_id, MastodonHelper.statusesPerCall(requireActivity())) - .observe(getViewLifecycleOwner(), statusesBottom -> dealWithPagination(statusesBottom, DIRECTION.TOP, fetchingMissing)); - } else if (direction == DIRECTION.REFRESH || direction == DIRECTION.SCROLL_TOP) { - timelinesVM.getHashTag(BaseMainActivity.currentToken, BaseMainActivity.currentInstance, tagTimeline.name, false, tagTimeline.isART, tagTimeline.all, tagTimeline.any, tagTimeline.none, null, null, null, MastodonHelper.statusesPerCall(requireActivity())) - .observe(getViewLifecycleOwner(), statusesRefresh -> { - if (statusAdapter != null) { - dealWithPagination(statusesRefresh, direction, true); - } else { - initializeStatusesCommonView(statusesRefresh); - } - }); - } + routeCommon(direction, fetchingMissing); } else if (timelineType == Timeline.TimeLineEnum.ACCOUNT_TIMELINE) { //PROFILE TIMELINES if (direction == null) { if (show_pinned) { @@ -875,110 +850,6 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. } - /** - * Load home timeline strategy - * - * @param direction - DIRECTION enum - */ - private void loadHomeStrategy(DIRECTION direction, boolean fetchingMissing) { - //When no direction is provided, it means it's the first call - if (direction == null && !fetchingMissing) { - //Two ways, depending of the Internet connection - //Connection is available toots are loaded remotely - if (networkAvailable == BaseMainActivity.status.CONNECTED) { - boolean fetchMarker = false; - if (markers.isEmpty()) { - markers.add("home"); - fetchMarker = true; - } - //We search for marker only once - It should not be fetched again when pull to refresh - if (fetchMarker && !binding.swipeContainer.isRefreshing()) { - //Search for last position - timelinesVM.getMarker(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, markers).observe(getViewLifecycleOwner(), marker -> { - if (marker != null) { - Marker.MarkerContent markerContent = marker.home; - if (markerContent != null) { - max_id = markerContent.last_read_id; - min_id = markerContent.last_read_id; - } else { - max_id = null; - } - } else { - max_id = null; - } - timelinesVM.getHome(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, BaseMainActivity.currentUserID, false, max_id, null, null, MastodonHelper.statusesPerCall(requireActivity()), false) - .observe(getViewLifecycleOwner(), this::initializeStatusesCommonView); - }); - } else { - timelinesVM.getHome(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, BaseMainActivity.currentUserID, false, max_id, null, null, MastodonHelper.statusesPerCall(requireActivity()), false) - .observe(getViewLifecycleOwner(), this::initializeStatusesCommonView); - } - - } else { - timelinesVM.getHomeCache(BaseMainActivity.currentInstance, BaseMainActivity.currentUserID, null, null, null) - .observe(getViewLifecycleOwner(), cachedStatus -> { - if (cachedStatus != null && cachedStatus.statuses != null) { - initializeStatusesCommonView(cachedStatus); - } - }); - - } - } else if (direction == DIRECTION.BOTTOM) { - if (!fetchingMissing) { - if (networkAvailable == BaseMainActivity.status.CONNECTED) { - //We first if we get results from cache - timelinesVM.getHomeCache(BaseMainActivity.currentInstance, BaseMainActivity.currentUserID, max_id, null, null) - .observe(getViewLifecycleOwner(), statusesBottomCache -> { - if (statusesBottomCache != null && statusesBottomCache.statuses != null && statusesBottomCache.statuses.size() > 0) { - dealWithPagination(statusesBottomCache, DIRECTION.BOTTOM, false); - } else { // If not, we fetch remotely - timelinesVM.getHome(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, BaseMainActivity.currentUserID, false, max_id, null, null, MastodonHelper.statusesPerCall(requireActivity()), false) - .observe(getViewLifecycleOwner(), statusesBottom -> dealWithPagination(statusesBottom, DIRECTION.BOTTOM, false)); - } - }); - - } else { - timelinesVM.getHomeCache(BaseMainActivity.currentInstance, BaseMainActivity.currentUserID, max_id, null, null) - .observe(getViewLifecycleOwner(), statusesBottom -> dealWithPagination(statusesBottom, DIRECTION.BOTTOM, false)); - } - } else { - timelinesVM.getHome(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, BaseMainActivity.currentUserID, true, max_id_fetch_more, null, null, MastodonHelper.statusesPerCall(requireActivity()), false) - .observe(getViewLifecycleOwner(), statusesBottom -> dealWithPagination(statusesBottom, DIRECTION.BOTTOM, true)); - } - } else if (direction == DIRECTION.TOP) { - if (!fetchingMissing) { - if (networkAvailable == BaseMainActivity.status.CONNECTED) { - timelinesVM.getHomeCache(BaseMainActivity.currentInstance, BaseMainActivity.currentUserID, null, min_id, null) - .observe(getViewLifecycleOwner(), statusesTopCache -> { - if (statusesTopCache != null && statusesTopCache.statuses != null && statusesTopCache.statuses.size() > 0) { - dealWithPagination(statusesTopCache, DIRECTION.TOP, false); - } else { - timelinesVM.getHome(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, BaseMainActivity.currentUserID, false, null, null, min_id, MastodonHelper.statusesPerCall(requireActivity()), false) - .observe(getViewLifecycleOwner(), statusesTop -> dealWithPagination(statusesTop, DIRECTION.TOP, false)); - } - }); - } else { - timelinesVM.getHomeCache(BaseMainActivity.currentInstance, BaseMainActivity.currentUserID, null, min_id, null) - .observe(getViewLifecycleOwner(), statusesTop -> dealWithPagination(statusesTop, DIRECTION.TOP, false)); - } - } else { - timelinesVM.getHome(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, BaseMainActivity.currentUserID, true, null, null, min_id_fetch_more, MastodonHelper.statusesPerCall(requireActivity()), false) - .observe(getViewLifecycleOwner(), statusesTop -> dealWithPagination(statusesTop, DIRECTION.TOP, true)); - } - - } else if (direction == DIRECTION.REFRESH || direction == DIRECTION.SCROLL_TOP) { - timelinesVM.getHome(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, BaseMainActivity.currentUserID, true, null, null, null, MastodonHelper.statusesPerCall(requireActivity()), false) - .observe(getViewLifecycleOwner(), statusRefresh -> { - if (statusAdapter != null) { - dealWithPagination(statusRefresh, direction, true); - } else { - initializeStatusesCommonView(statusRefresh); - } - } - ); - } - } - /** * Refresh status in list diff --git a/app/src/main/java/app/fedilab/android/viewmodel/mastodon/TimelinesVM.java b/app/src/main/java/app/fedilab/android/viewmodel/mastodon/TimelinesVM.java index bd1e7a71..a7ca3a7f 100644 --- a/app/src/main/java/app/fedilab/android/viewmodel/mastodon/TimelinesVM.java +++ b/app/src/main/java/app/fedilab/android/viewmodel/mastodon/TimelinesVM.java @@ -22,6 +22,7 @@ import android.os.Handler; import android.os.Looper; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.lifecycle.AndroidViewModel; import androidx.lifecycle.LiveData; import androidx.lifecycle.MutableLiveData; @@ -32,6 +33,7 @@ import java.util.List; import java.util.concurrent.TimeUnit; import app.fedilab.android.R; +import app.fedilab.android.activities.MainActivity; import app.fedilab.android.client.endpoints.MastodonTimelinesService; import app.fedilab.android.client.entities.api.Account; import app.fedilab.android.client.entities.api.Conversation; @@ -45,6 +47,7 @@ import app.fedilab.android.client.entities.api.Tag; import app.fedilab.android.client.entities.app.BaseAccount; import app.fedilab.android.client.entities.app.StatusCache; import app.fedilab.android.client.entities.app.StatusDraft; +import app.fedilab.android.client.entities.app.Timeline; import app.fedilab.android.client.entities.misskey.MisskeyNote; import app.fedilab.android.client.entities.nitter.Nitter; import app.fedilab.android.client.entities.peertube.PeertubeVideo; @@ -52,6 +55,7 @@ import app.fedilab.android.exception.DBException; import app.fedilab.android.helper.Helper; import app.fedilab.android.helper.MastodonHelper; import app.fedilab.android.helper.TimelineHelper; +import app.fedilab.android.ui.fragment.timeline.FragmentMastodonTimeline; import okhttp3.OkHttpClient; import retrofit2.Call; import retrofit2.Response; @@ -162,50 +166,6 @@ public class TimelinesVM extends AndroidViewModel { return tagListMutableLiveData; } - /** - * Public timeline - * - * @param local Show only local statuses? Defaults to false. - * @param remote Show only remote statuses? Defaults to false. - * @param onlyMedia Show only statuses with media attached? Defaults to false. - * @param maxId Return results older than this id - * @param sinceId Return results newer than this id - * @param minId Return results immediately newer than this id - * @param limit Maximum number of results to return. Defaults to 20. - * @return {@link LiveData} containing a {@link Statuses} - */ - public LiveData getPublic(String token, @NonNull String instance, - Boolean local, - Boolean remote, - Boolean onlyMedia, - String maxId, - String sinceId, - String minId, - Integer limit) { - MastodonTimelinesService mastodonTimelinesService = init(instance); - statusesMutableLiveData = new MutableLiveData<>(); - new Thread(() -> { - Call> publicTlCall = mastodonTimelinesService.getPublic(token, local, remote, onlyMedia, maxId, sinceId, minId, limit); - Statuses statuses = new Statuses(); - if (publicTlCall != null) { - try { - Response> publicTlResponse = publicTlCall.execute(); - if (publicTlResponse.isSuccessful()) { - List notFilteredStatuses = publicTlResponse.body(); - statuses.statuses = TimelineHelper.filterStatus(getApplication(), notFilteredStatuses, TimelineHelper.FilterTimeLineType.PUBLIC); - statuses.pagination = MastodonHelper.getPagination(publicTlResponse.headers()); - } - } catch (Exception e) { - e.printStackTrace(); - } - } - Handler mainHandler = new Handler(Looper.getMainLooper()); - Runnable myRunnable = () -> statusesMutableLiveData.setValue(statuses); - mainHandler.post(myRunnable); - }).start(); - return statusesMutableLiveData; - } - /** * Public timeline for Nitter @@ -382,96 +342,65 @@ public class TimelinesVM extends AndroidViewModel { } - /** - * View public statuses containing the given hashtag. - * - * @param hashtag Content of a #hashtag, not including # symbol. - * @param local If true, return only local statuses. Defaults to false. - * @param onlyMedia If true, return only statuses with media attachments. Defaults to false. - * @param maxId Return results older than this ID. - * @param sinceId Return results newer than this ID. - * @param minId Return results immediately newer than this ID. - * @param limit Maximum number of results to return. Defaults to 20. - * @return {@link LiveData} containing a {@link Statuses} - */ - public LiveData getHashTag(String token, @NonNull String instance, - @NonNull String hashtag, - boolean local, - boolean onlyMedia, - List all, - List any, - List none, - String maxId, - String sinceId, - String minId, - int limit) { - statusesMutableLiveData = new MutableLiveData<>(); - MastodonTimelinesService mastodonTimelinesService = init(instance); - new Thread(() -> { - Statuses statuses = new Statuses(); - String hashtagTrim = hashtag.replaceAll("\\#", ""); - Call> hashTagTlCall = mastodonTimelinesService.getHashTag(token, hashtagTrim, local, onlyMedia, all, any, none, maxId, sinceId, minId, limit); - if (hashTagTlCall != null) { - try { - Response> hashTagTlResponse = hashTagTlCall.execute(); - if (hashTagTlResponse.isSuccessful()) { - List notFilteredStatuses = hashTagTlResponse.body(); - statuses.statuses = TimelineHelper.filterStatus(getApplication().getApplicationContext(), notFilteredStatuses, TimelineHelper.FilterTimeLineType.PUBLIC); - statuses.pagination = MastodonHelper.getPagination(hashTagTlResponse.headers()); - } - } catch (Exception e) { - e.printStackTrace(); - } - } - Handler mainHandler = new Handler(Looper.getMainLooper()); - Runnable myRunnable = () -> statusesMutableLiveData.setValue(statuses); - mainHandler.post(myRunnable); - }).start(); - - return statusesMutableLiveData; - } + public LiveData getTimeline(TimelineParams timelineParams) { - /** - * View statuses from followed users. - * - * @param maxId Return results older than id - * @param sinceId Return results newer than id - * @param minId Return results immediately newer than id - * @param limit Maximum number of results to return. Defaults to 20. - * @param local Return only local statuses? - * @return {@link LiveData} containing a {@link Statuses} - */ - public LiveData getHome(@NonNull String instance, String token, - String userId, - boolean fetchingMissing, - String maxId, - String sinceId, - String minId, - int limit, - boolean local) { statusesMutableLiveData = new MutableLiveData<>(); - MastodonTimelinesService mastodonTimelinesService = init(instance); + MastodonTimelinesService mastodonTimelinesService = init(timelineParams.instance); new Thread(() -> { Statuses statuses = new Statuses(); - Call> homeTlCall = mastodonTimelinesService.getHome(token, maxId, sinceId, minId, limit, local); - if (homeTlCall != null) { + Call> timelineCall = null; + switch (timelineParams.type) { + case HOME: + timelineCall = mastodonTimelinesService.getHome(timelineParams.token, timelineParams.maxId, timelineParams.sinceId, timelineParams.minId, timelineParams.limit, timelineParams.local); + break; + case LOCAL: + timelineCall = mastodonTimelinesService.getPublic(timelineParams.token, true, false, timelineParams.onlyMedia, timelineParams.maxId, timelineParams.sinceId, timelineParams.minId, timelineParams.limit); + break; + case PUBLIC: + timelineCall = mastodonTimelinesService.getPublic(timelineParams.token, false, true, timelineParams.onlyMedia, timelineParams.maxId, timelineParams.sinceId, timelineParams.minId, timelineParams.limit); + break; + case TAG: + timelineCall = mastodonTimelinesService.getHashTag(timelineParams.token, timelineParams.hashtagTrim, timelineParams.local, timelineParams.onlyMedia, timelineParams.all, timelineParams.any, timelineParams.none, timelineParams.maxId, timelineParams.sinceId, timelineParams.minId, timelineParams.limit); + break; + case LIST: + timelineCall = mastodonTimelinesService.getList(timelineParams.token, timelineParams.listId, timelineParams.maxId, timelineParams.sinceId, timelineParams.minId, timelineParams.limit); + break; + } + if (timelineCall != null) { try { - Response> homeTlResponse = homeTlCall.execute(); - if (homeTlResponse.isSuccessful()) { - List notFilteredStatuses = homeTlResponse.body(); - statuses.statuses = TimelineHelper.filterStatus(getApplication().getApplicationContext(), notFilteredStatuses, TimelineHelper.FilterTimeLineType.HOME); - statuses.pagination = MastodonHelper.getPagination(homeTlResponse.headers()); - if (!fetchingMissing) { + Response> timelineResponse = timelineCall.execute(); + if (timelineResponse.isSuccessful()) { + List statusList = timelineResponse.body(); + statuses.statuses = TimelineHelper.filterStatus(getApplication().getApplicationContext(), statusList, TimelineHelper.FilterTimeLineType.PUBLIC); + statuses.pagination = MastodonHelper.getPagination(timelineResponse.headers()); + if (statusList != null && statusList.size() > 0) { + if (timelineParams.direction == FragmentMastodonTimeline.DIRECTION.REFRESH || timelineParams.direction == FragmentMastodonTimeline.DIRECTION.SCROLL_TOP) { + Status newestStatus = new StatusCache(getApplication().getApplicationContext()).getNewestStatus(timelineParams.slug, timelineParams.instance, timelineParams.userId); + //When refreshing/scrolling to TOP, if last statuses fetched has a greater id from newest in cache, there is potential hole + if (newestStatus != null && statusList.get(statusList.size() - 1).id.compareToIgnoreCase(newestStatus.id) > 0) { + statusList.get(statusList.size() - 1).isFetchMore = true; + } + } else if (timelineParams.direction == FragmentMastodonTimeline.DIRECTION.TOP && timelineParams.fetchingMissing) { + Status topStatus = new StatusCache(getApplication().getApplicationContext()).getTopFetchMore(timelineParams.slug, timelineParams.instance, timelineParams.slug, statusList.get(0).id); + if (topStatus != null && statusList.get(0).id.compareToIgnoreCase(topStatus.id) < 0) { + statusList.get(0).isFetchMore = true; + } + } else if (timelineParams.direction == FragmentMastodonTimeline.DIRECTION.BOTTOM && timelineParams.fetchingMissing) { + Status bottomStatus = new StatusCache(getApplication().getApplicationContext()).getBottomFetchMore(timelineParams.slug, timelineParams.instance, timelineParams.slug, statusList.get(0).id); + if (bottomStatus != null && statusList.get(statusList.size() - 1).id.compareToIgnoreCase(bottomStatus.id) > 0) { + statusList.get(statusList.size() - 1).isFetchMore = true; + } + } for (Status status : statuses.statuses) { StatusCache statusCacheDAO = new StatusCache(getApplication().getApplicationContext()); StatusCache statusCache = new StatusCache(); - statusCache.instance = instance; - statusCache.user_id = userId; + statusCache.instance = timelineParams.instance; + statusCache.user_id = timelineParams.userId; statusCache.status = status; - statusCache.type = StatusCache.CacheEnum.HOME; + statusCache.type = timelineParams.type; statusCache.status_id = status.id; try { - statusCacheDAO.insertOrUpdate(statusCache); + statusCacheDAO.insertOrUpdate(statusCache, timelineParams.slug); } catch (DBException e) { e.printStackTrace(); } @@ -486,32 +415,18 @@ public class TimelinesVM extends AndroidViewModel { Runnable myRunnable = () -> statusesMutableLiveData.setValue(statuses); mainHandler.post(myRunnable); }).start(); - return statusesMutableLiveData; } - /** - * Get home status from cache - * - * @param instance String - instance - * @param user_id String - user id - * @param maxId String - max id - * @param minId String - min id - * @return LiveData - */ - public LiveData getHomeCache(@NonNull String instance, String user_id, - String maxId, - String minId, - String sinceId) { + public LiveData getTimelineCache(TimelineParams timelineParams) { statusesMutableLiveData = new MutableLiveData<>(); new Thread(() -> { StatusCache statusCacheDAO = new StatusCache(getApplication().getApplicationContext()); Statuses statuses = null; try { - statuses = statusCacheDAO.geStatuses(StatusCache.CacheEnum.HOME, instance, user_id, maxId, minId, sinceId); - + statuses = statusCacheDAO.geStatuses(timelineParams.slug, timelineParams.instance, timelineParams.userId, timelineParams.maxId, timelineParams.minId, timelineParams.sinceId); if (statuses != null) { - statuses.statuses = TimelineHelper.filterStatus(getApplication().getApplicationContext(), statuses.statuses, TimelineHelper.FilterTimeLineType.HOME); + TimelineHelper.filterStatus(getApplication().getApplicationContext(), statuses.statuses, TimelineHelper.FilterTimeLineType.HOME); if (statuses.statuses != null && statuses.statuses.size() > 0) { statuses.pagination = new Pagination(); statuses.pagination.min_id = statuses.statuses.get(0).id; @@ -529,6 +444,42 @@ public class TimelinesVM extends AndroidViewModel { return statusesMutableLiveData; } + public static class TimelineParams { + + public FragmentMastodonTimeline.DIRECTION direction; + public String instance; + public String token; + public Timeline.TimeLineEnum type; + public String slug; + public String userId; + public Boolean remote; + public Boolean onlyMedia; + public String hashtagTrim; + public List all; + public List any; + public List none; + public String listId; + public Boolean fetchingMissing; + public String maxId; + public String sinceId; + public String minId; + public int limit = 40; + public Boolean local; + + public TimelineParams(@NonNull Timeline.TimeLineEnum type, @Nullable FragmentMastodonTimeline.DIRECTION direction, @Nullable String ident) { + if (type != Timeline.TimeLineEnum.REMOTE) { + instance = MainActivity.currentInstance; + token = MainActivity.currentToken; + userId = MainActivity.currentUserID; + } + String key = type.getValue(); + if (ident != null) { + key += "|" + ident; + } + slug = key; + } + } + /** * Get user drafts @@ -553,45 +504,6 @@ public class TimelinesVM extends AndroidViewModel { return statusDraftListMutableLiveData; } - /** - * View statuses in the given list timeline. - * - * @param listId Local ID of the list in the database. - * @param maxId Return results older than this ID. - * @param sinceId Return results newer than this ID. - * @param minId Return results immediately newer than this ID. - * @param limit Maximum number of results to return. Defaults to 20.Return results older than this ID. - * @return {@link LiveData} containing a {@link Statuses} - */ - public LiveData getList(@NonNull String instance, String token, - @NonNull String listId, - String maxId, - String sinceId, - String minId, - int limit) { - statusesMutableLiveData = new MutableLiveData<>(); - MastodonTimelinesService mastodonTimelinesService = init(instance); - new Thread(() -> { - Statuses statuses = new Statuses(); - Call> listTlCall = mastodonTimelinesService.getList(token, listId, maxId, sinceId, minId, limit); - if (listTlCall != null) { - try { - Response> listTlResponse = listTlCall.execute(); - if (listTlResponse.isSuccessful()) { - statuses.statuses = listTlResponse.body(); - statuses.pagination = MastodonHelper.getPagination(listTlResponse.headers()); - } - } catch (Exception e) { - e.printStackTrace(); - } - } - Handler mainHandler = new Handler(Looper.getMainLooper()); - Runnable myRunnable = () -> statusesMutableLiveData.setValue(statuses); - mainHandler.post(myRunnable); - }).start(); - - return statusesMutableLiveData; - } /** * Show conversations diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e02528b9..a959c9c3 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -906,6 +906,8 @@ SET_DISPLAY_BOOKMARK SET_NOTIF_VALIDATION_FAV SET_DISPLAY_COUNTER_FAV_BOOST + SET_HOME_INNER_MARKER + SET_NOTIF_SILENT SET_EXPAND_CW SET_DISPLAY_ALL_NOTIFICATIONS_TYPE