From a7dbb946f03cd01de342204c3390feb13cf84529 Mon Sep 17 00:00:00 2001 From: Rodrigo Martins Date: Thu, 2 Jul 2026 20:51:39 -0300 Subject: [PATCH 01/22] [pagination-scroll] - creates option to enable/disable infinite scroll. --- app/src/main/java/com/jerboa/db/AppDB.kt | 4 +++- app/src/main/java/com/jerboa/db/AppDBMigrations.kt | 10 ++++++++++ .../main/java/com/jerboa/db/entity/AppSettings.kt | 6 ++++++ .../settings/lookandfeel/LookAndFeelScreen.kt | 13 +++++++++++++ app/src/main/res/values/strings.xml | 1 + 5 files changed, 33 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/jerboa/db/AppDB.kt b/app/src/main/java/com/jerboa/db/AppDB.kt index 91a5dad08..2f83a75ad 100644 --- a/app/src/main/java/com/jerboa/db/AppDB.kt +++ b/app/src/main/java/com/jerboa/db/AppDB.kt @@ -27,6 +27,7 @@ const val DEFAULT_LAST_VERSION_CODE_VIEWED = 0 const val DEFAULT_POST_VIEW_MODE = 0 const val DEFAULT_POST_NAVIGATION_GESTURE_MODE = 0 const val DEFAULT_SHOW_BOTTOM_NAV = 1 +const val DEFAULT_ENABLE_IFINITE_SCROLL = 1 const val DEFAULT_SHOW_COLLAPSED_COMMENT_CONTENT = 0 const val DEFAULT_SHOW_COMMENT_ACTION_BAR_BY_DEFAULT = 1 const val DEFAULT_SHOW_VOTING_ARROWS_IN_LIST_VIEW = 1 @@ -74,10 +75,11 @@ val APP_SETTINGS_DEFAULT = swipeToActionPreset = DEFAULT_SWIPE_TO_ACTION_PRESET, disableVideoAutoplay = DEFAULT_DISABLE_AUTO_PLAY, lowBandwidthMode = DEFAULT_LOW_BANDWIDTH_MODE, + enableInfiniteScroll = DEFAULT_ENABLE_IFINITE_SCROLL.toBool() ) @Database( - version = 35, + version = 36, entities = [Account::class, AppSettings::class], exportSchema = true, ) diff --git a/app/src/main/java/com/jerboa/db/AppDBMigrations.kt b/app/src/main/java/com/jerboa/db/AppDBMigrations.kt index fb66fad88..77c0ca47d 100644 --- a/app/src/main/java/com/jerboa/db/AppDBMigrations.kt +++ b/app/src/main/java/com/jerboa/db/AppDBMigrations.kt @@ -575,6 +575,15 @@ val MIGRATION_34_35 = } } +val MIGRATION_35_36 = + object : Migration(35, 36) { + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL( + "ALTER TABLE AppSettings ADD COLUMN enable_infinite_scroll INTEGER NOT NULL DEFAULT 1", + ) + } + } + // Don't forget to test your migration with `./gradlew app:connectAndroidTest` val MIGRATIONS_LIST = arrayOf( @@ -612,4 +621,5 @@ val MIGRATIONS_LIST = MIGRATION_32_33, MIGRATION_33_34, MIGRATION_34_35, + MIGRATION_35_36 ) diff --git a/app/src/main/java/com/jerboa/db/entity/AppSettings.kt b/app/src/main/java/com/jerboa/db/entity/AppSettings.kt index e38f8e8a4..dd8545820 100644 --- a/app/src/main/java/com/jerboa/db/entity/AppSettings.kt +++ b/app/src/main/java/com/jerboa/db/entity/AppSettings.kt @@ -7,6 +7,7 @@ import com.jerboa.db.DEFAULT_AUTO_PLAY_GIFS import com.jerboa.db.DEFAULT_BACK_CONFIRMATION_MODE import com.jerboa.db.DEFAULT_BLUR_NSFW import com.jerboa.db.DEFAULT_DISABLE_AUTO_PLAY +import com.jerboa.db.DEFAULT_ENABLE_IFINITE_SCROLL import com.jerboa.db.DEFAULT_LAST_VERSION_CODE_VIEWED import com.jerboa.db.DEFAULT_LOW_BANDWIDTH_MODE import com.jerboa.db.DEFAULT_MARK_AS_READ_ON_SCROLL @@ -58,6 +59,11 @@ data class AppSettings( defaultValue = DEFAULT_SHOW_BOTTOM_NAV.toString(), ) val showBottomNav: Boolean, + @ColumnInfo( + name = "enable_infinite_scroll", + defaultValue = DEFAULT_ENABLE_IFINITE_SCROLL.toString(), + ) + val enableInfiniteScroll: Boolean, @ColumnInfo( name = "post_navigation_gesture_mode", defaultValue = DEFAULT_POST_NAVIGATION_GESTURE_MODE.toString(), diff --git a/app/src/main/java/com/jerboa/ui/components/settings/lookandfeel/LookAndFeelScreen.kt b/app/src/main/java/com/jerboa/ui/components/settings/lookandfeel/LookAndFeelScreen.kt index a9879a3b8..f0fdaec21 100644 --- a/app/src/main/java/com/jerboa/ui/components/settings/lookandfeel/LookAndFeelScreen.kt +++ b/app/src/main/java/com/jerboa/ui/components/settings/lookandfeel/LookAndFeelScreen.kt @@ -88,6 +88,7 @@ fun LookAndFeelScreen( var swipeToActionPresetState by remember { mutableStateOf(SwipeToActionPreset.entries[settings.swipeToActionPreset]) } var showBottomNavState by remember { mutableStateOf(settings.showBottomNav) } + var enableInfiniteScrollState by remember { mutableStateOf(settings.enableInfiniteScroll) } var showTextDescriptionsInNavbarState by remember { mutableStateOf(settings.showTextDescriptionsInNavbar) } var showCollapsedCommentContentState by remember { mutableStateOf(settings.showCollapsedCommentContent) } var showCommentActionBarByDefaultState by remember { mutableStateOf(settings.showCommentActionBarByDefault) } @@ -117,6 +118,7 @@ fun LookAndFeelScreen( fontSize = fontSizeState.toInt(), postViewMode = postViewModeState.ordinal, showBottomNav = showBottomNavState, + enableInfiniteScroll = enableInfiniteScrollState, showCollapsedCommentContent = showCollapsedCommentContentState, showCommentActionBarByDefault = showCommentActionBarByDefaultState, showVotingArrowsInListView = showVotingArrowsInListViewState, @@ -429,6 +431,17 @@ fun LookAndFeelScreen( }, ) + SwitchPreference( + value = enableInfiniteScrollState, + onValueChange = { + enableInfiniteScrollState = it + updateAppSettings() + }, + title = { + Text(text = stringResource(R.string.look_and_feel_enable_infinite_scroll)) + }, + ) + SwitchPreference( enabled = showBottomNavState, value = showTextDescriptionsInNavbarState, diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 21b080010..0af5b2efc 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -157,6 +157,7 @@ Prevent screenshots Show action bar by default for comments Show navigation bar + Enable infinite scroll Show text descriptions in navigation bar Show voting arrows in list view Show parent comment navigation buttons From 3fe222a5b5c1e102a215c9e5797a20992be5a6fc Mon Sep 17 00:00:00 2001 From: Rodrigo Martins Date: Fri, 3 Jul 2026 21:47:19 -0300 Subject: [PATCH 02/22] [pagination-scroll] - creates pagination buttons. --- app/src/main/java/com/jerboa/MainActivity.kt | 4 +++ .../jerboa/ui/components/common/Buttons.kt | 35 +++++++++++++++++++ .../components/community/CommunityScreen.kt | 2 ++ .../ui/components/home/BottomNavScreen.kt | 5 ++- .../jerboa/ui/components/home/HomeActivity.kt | 4 +++ .../components/person/PersonProfileScreen.kt | 4 +++ .../jerboa/ui/components/post/PostListings.kt | 17 ++++++++- app/src/main/res/values/strings.xml | 3 ++ 8 files changed, 72 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/jerboa/MainActivity.kt b/app/src/main/java/com/jerboa/MainActivity.kt index 38cbecc7e..09e1b2dba 100644 --- a/app/src/main/java/com/jerboa/MainActivity.kt +++ b/app/src/main/java/com/jerboa/MainActivity.kt @@ -272,6 +272,7 @@ class MainActivity : AppCompatActivity() { swipeToActionPreset = appSettings.swipeToActionPreset.toEnum(), disableVideoAutoplay = appSettings.disableVideoAutoplay.toBool(), lowBandwidthMode = lowBandwidthMode, + enableInfiniteScroll = appSettings.enableInfiniteScroll ) } @@ -318,6 +319,7 @@ class MainActivity : AppCompatActivity() { swipeToActionPreset = appSettings.swipeToActionPreset.toEnum(), disableVideoAutoplay = appSettings.disableVideoAutoplay.toBool(), lowBandwidthMode = lowBandwidthMode, + enableInfiniteScroll = appSettings.enableInfiniteScroll ) } @@ -362,6 +364,7 @@ class MainActivity : AppCompatActivity() { swipeToActionPreset = appSettings.swipeToActionPreset.toEnum(), disableVideoAutoplay = appSettings.disableVideoAutoplay.toBool(), lowBandwidthMode = lowBandwidthMode, + enableInfiniteScroll = appSettings.enableInfiniteScroll ) } @@ -403,6 +406,7 @@ class MainActivity : AppCompatActivity() { swipeToActionPreset = appSettings.swipeToActionPreset.toEnum(), disableVideoAutoplay = appSettings.disableVideoAutoplay.toBool(), lowBandwidthMode = lowBandwidthMode, + enableInfiniteScroll = appSettings.enableInfiniteScroll ) } diff --git a/app/src/main/java/com/jerboa/ui/components/common/Buttons.kt b/app/src/main/java/com/jerboa/ui/components/common/Buttons.kt index 316e8d875..a239005c9 100644 --- a/app/src/main/java/com/jerboa/ui/components/common/Buttons.kt +++ b/app/src/main/java/com/jerboa/ui/components/common/Buttons.kt @@ -1,5 +1,7 @@ package com.jerboa.ui.components.common +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.material3.Button @@ -7,8 +9,10 @@ import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview import com.jerboa.R import com.jerboa.ui.theme.XXL_PADDING @@ -29,3 +33,34 @@ fun RetryLoadingPosts(onClick: () -> Unit) { Text(stringResource(R.string.posts_failed_loading)) } } + +@Composable +fun PaginationButton( + currentPage: Int, + onNext: () -> Unit, + onPrevious: () -> Unit, +) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Button(onClick = onPrevious) { + Text(stringResource(R.string.previous)) + } + Text(stringResource(R.string.page_number, currentPage)) + Button(onClick = onNext) { + Text(stringResource(R.string.next)) + } + } +} + +@Composable +@Preview(showBackground = true) +private fun PaginationButtonPreview() { + PaginationButton( + currentPage = 1, + onNext = { }, + onPrevious = { } + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/jerboa/ui/components/community/CommunityScreen.kt b/app/src/main/java/com/jerboa/ui/components/community/CommunityScreen.kt index d7891f693..cc446e859 100644 --- a/app/src/main/java/com/jerboa/ui/components/community/CommunityScreen.kt +++ b/app/src/main/java/com/jerboa/ui/components/community/CommunityScreen.kt @@ -90,6 +90,7 @@ fun CommunityScreen( swipeToActionPreset: SwipeToActionPreset, disableVideoAutoplay: Boolean, lowBandwidthMode: Boolean, + enableInfiniteScroll: Boolean, ) { Log.d("jerboa", "got to community screen") @@ -457,6 +458,7 @@ fun CommunityScreen( swipeToActionPreset = swipeToActionPreset, disableVideoAutoplay = disableVideoAutoplay, lowBandwidthMode = lowBandwidthMode, + enableInfiniteScroll = enableInfiniteScroll ) } diff --git a/app/src/main/java/com/jerboa/ui/components/home/BottomNavScreen.kt b/app/src/main/java/com/jerboa/ui/components/home/BottomNavScreen.kt index d588cb06a..42dd5e12a 100644 --- a/app/src/main/java/com/jerboa/ui/components/home/BottomNavScreen.kt +++ b/app/src/main/java/com/jerboa/ui/components/home/BottomNavScreen.kt @@ -268,6 +268,7 @@ fun BottomNavScreen( disableVideoAutoplay = appSettings.disableVideoAutoplay.toBool(), lowBandwidthMode = lowBandwidthMode, padding = padding, + enableInfiniteScroll = appSettings.enableInfiniteScroll ) } @@ -338,7 +339,8 @@ fun BottomNavScreen( swipeToActionPreset = appSettings.swipeToActionPreset.toEnum(), disableVideoAutoplay = appSettings.disableVideoAutoplay.toBool(), lowBandwidthMode = lowBandwidthMode, - padding = padding, + enableInfiniteScroll = appSettings.enableInfiniteScroll, + padding = padding ) } @@ -362,6 +364,7 @@ fun BottomNavScreen( swipeToActionPreset = appSettings.swipeToActionPreset.toEnum(), disableVideoAutoplay = appSettings.disableVideoAutoplay.toBool(), lowBandwidthMode = lowBandwidthMode, + enableInfiniteScroll = appSettings.enableInfiniteScroll, padding = padding, ) } diff --git a/app/src/main/java/com/jerboa/ui/components/home/HomeActivity.kt b/app/src/main/java/com/jerboa/ui/components/home/HomeActivity.kt index f4fd36127..c18af3c8f 100644 --- a/app/src/main/java/com/jerboa/ui/components/home/HomeActivity.kt +++ b/app/src/main/java/com/jerboa/ui/components/home/HomeActivity.kt @@ -99,6 +99,7 @@ fun HomeScreen( disableVideoAutoplay: Boolean, lowBandwidthMode: Boolean, padding: PaddingValues, + enableInfiniteScroll: Boolean ) { Log.d("jerboa", "got to home screen") @@ -183,6 +184,7 @@ fun HomeScreen( swipeToActionPreset = swipeToActionPreset, disableVideoAutoplay = disableVideoAutoplay, lowBandwidthMode = lowBandwidthMode, + enableInfiniteScroll = enableInfiniteScroll ) } }, @@ -235,6 +237,7 @@ fun MainPostListingsContent( swipeToActionPreset: SwipeToActionPreset, disableVideoAutoplay: Boolean, lowBandwidthMode: Boolean, + enableInfiniteScroll: Boolean ) { val ctx = LocalContext.current val resources = LocalResources.current @@ -471,6 +474,7 @@ fun MainPostListingsContent( swipeToActionPreset = swipeToActionPreset, disableVideoAutoplay = disableVideoAutoplay, lowBandwidthMode = lowBandwidthMode, + enableInfiniteScroll = enableInfiniteScroll ) } } diff --git a/app/src/main/java/com/jerboa/ui/components/person/PersonProfileScreen.kt b/app/src/main/java/com/jerboa/ui/components/person/PersonProfileScreen.kt index 49202f7cf..a72b91930 100644 --- a/app/src/main/java/com/jerboa/ui/components/person/PersonProfileScreen.kt +++ b/app/src/main/java/com/jerboa/ui/components/person/PersonProfileScreen.kt @@ -127,6 +127,7 @@ fun PersonProfileScreen( swipeToActionPreset: SwipeToActionPreset, disableVideoAutoplay: Boolean, lowBandwidthMode: Boolean, + enableInfiniteScroll: Boolean, padding: PaddingValues? = null, ) { Log.d("jerboa", "got to person screen") @@ -319,6 +320,7 @@ fun PersonProfileScreen( swipeToActionPreset = swipeToActionPreset, disableVideoAutoplay = disableVideoAutoplay, lowBandwidthMode = lowBandwidthMode, + enableInfiniteScroll = enableInfiniteScroll ) } }, @@ -358,6 +360,7 @@ fun UserTabs( swipeToActionPreset: SwipeToActionPreset, disableVideoAutoplay: Boolean, lowBandwidthMode: Boolean, + enableInfiniteScroll: Boolean, ) { val tabTitles = if (savedMode) { @@ -681,6 +684,7 @@ fun UserTabs( swipeToActionPreset = swipeToActionPreset, disableVideoAutoplay = disableVideoAutoplay, lowBandwidthMode = lowBandwidthMode, + enableInfiniteScroll = enableInfiniteScroll ) } diff --git a/app/src/main/java/com/jerboa/ui/components/post/PostListings.kt b/app/src/main/java/com/jerboa/ui/components/post/PostListings.kt index 81900c983..b9fe029c2 100644 --- a/app/src/main/java/com/jerboa/ui/components/post/PostListings.kt +++ b/app/src/main/java/com/jerboa/ui/components/post/PostListings.kt @@ -1,6 +1,7 @@ package com.jerboa.ui.components.post import androidx.compose.foundation.layout.ExperimentalLayoutApi +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn @@ -27,6 +28,7 @@ import com.jerboa.feat.PostActionBarMode import com.jerboa.feat.SwipeToActionPreset import com.jerboa.feat.default import com.jerboa.rememberJerboaAppState +import com.jerboa.ui.components.common.PaginationButton import com.jerboa.ui.components.common.RetryLoadingPosts import com.jerboa.ui.components.common.TriggerWhenReachingEnd import com.jerboa.ui.theme.SMALL_PADDING @@ -69,6 +71,7 @@ fun PostListings( postViewMode: PostViewMode, showVotingArrowsInListView: Boolean, enableDownVotes: Boolean, + enableInfiniteScroll: Boolean, showAvatar: Boolean, useCustomTabs: Boolean, usePrivateTabs: Boolean, @@ -160,12 +163,23 @@ fun PostListings( RetryLoadingPosts(loadMorePosts) } } + + if (!enableInfiniteScroll) { + item(contentType = "pagination_buttons") { + Spacer(Modifier.padding(vertical = SMALL_PADDING)) + PaginationButton( + currentPage = 1, // TODO: pass correct value + onNext = { }, + onPrevious = { } + ) + } + } } TriggerWhenReachingEnd(listState, showPostAppendRetry, loadMorePosts) } -@Preview +@Preview(showBackground = true) @Composable fun PreviewPostListings() { PostListings( @@ -210,5 +224,6 @@ fun PreviewPostListings() { onReplyClick = {}, disableVideoAutoplay = false, lowBandwidthMode = false, + enableInfiniteScroll = false ) } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 0af5b2efc..7b1a10a5f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -510,4 +510,7 @@ Unmute Play Pause + Page %1$d + Previous + Next From 18ee28e2121d419877b2ed89a756f78bd8d242c0 Mon Sep 17 00:00:00 2001 From: rodrigo-fm Date: Fri, 3 Jul 2026 21:31:52 -0300 Subject: [PATCH 03/22] [pagination-scroll] - adds functionality to the next page button. --- .../main/java/com/jerboa/model/PostsViewModel.kt | 6 +++++- .../com/jerboa/ui/components/common/Buttons.kt | 12 ++++++++++-- .../ui/components/community/CommunityScreen.kt | 5 ++++- .../jerboa/ui/components/home/HomeActivity.kt | 5 ++++- .../ui/components/person/PersonProfileScreen.kt | 5 ++++- .../jerboa/ui/components/post/PostListings.kt | 16 +++++++++++----- 6 files changed, 38 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/com/jerboa/model/PostsViewModel.kt b/app/src/main/java/com/jerboa/model/PostsViewModel.kt index 0822e802c..5264f2a78 100644 --- a/app/src/main/java/com/jerboa/model/PostsViewModel.kt +++ b/app/src/main/java/com/jerboa/model/PostsViewModel.kt @@ -20,7 +20,6 @@ import com.jerboa.db.entity.AnonAccount import com.jerboa.db.repository.AccountRepository import com.jerboa.feed.PaginationController import com.jerboa.feed.PostController -import com.jerboa.findAndUpdatePostHidden import com.jerboa.toEnumSafe import it.vercruysse.lemmyapi.datatypes.CreatePostLike import it.vercruysse.lemmyapi.datatypes.DeletePost @@ -114,6 +113,11 @@ open class PostsViewModel( postController.findAndUpdatePostCreatorBannedFromCommunity(banData) } + fun nextPage() { + postController.clear() + initPosts(getForm(), ApiState.Loading) + } + fun resetPosts(state: ApiState> = ApiState.Loading) { pageController.reset() postController.clear() diff --git a/app/src/main/java/com/jerboa/ui/components/common/Buttons.kt b/app/src/main/java/com/jerboa/ui/components/common/Buttons.kt index a239005c9..bdb1a9370 100644 --- a/app/src/main/java/com/jerboa/ui/components/common/Buttons.kt +++ b/app/src/main/java/com/jerboa/ui/components/common/Buttons.kt @@ -36,12 +36,14 @@ fun RetryLoadingPosts(onClick: () -> Unit) { @Composable fun PaginationButton( - currentPage: Int, + currentPage: Long, onNext: () -> Unit, onPrevious: () -> Unit, ) { Row( - modifier = Modifier.fillMaxWidth(), + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = XXL_PADDING), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically ) { @@ -63,4 +65,10 @@ private fun PaginationButtonPreview() { onNext = { }, onPrevious = { } ) +} + +@Composable +@Preview(showBackground = true) +private fun RetryLoadingPostsPreview() { + RetryLoadingPosts { } } \ No newline at end of file diff --git a/app/src/main/java/com/jerboa/ui/components/community/CommunityScreen.kt b/app/src/main/java/com/jerboa/ui/components/community/CommunityScreen.kt index cc446e859..2e47fd7d6 100644 --- a/app/src/main/java/com/jerboa/ui/components/community/CommunityScreen.kt +++ b/app/src/main/java/com/jerboa/ui/components/community/CommunityScreen.kt @@ -458,7 +458,10 @@ fun CommunityScreen( swipeToActionPreset = swipeToActionPreset, disableVideoAutoplay = disableVideoAutoplay, lowBandwidthMode = lowBandwidthMode, - enableInfiniteScroll = enableInfiniteScroll + enableInfiniteScroll = enableInfiniteScroll, + onNextPage = { }, + onPreviousPage = { }, + currentPage = 1 ) } diff --git a/app/src/main/java/com/jerboa/ui/components/home/HomeActivity.kt b/app/src/main/java/com/jerboa/ui/components/home/HomeActivity.kt index c18af3c8f..6443cb074 100644 --- a/app/src/main/java/com/jerboa/ui/components/home/HomeActivity.kt +++ b/app/src/main/java/com/jerboa/ui/components/home/HomeActivity.kt @@ -474,7 +474,10 @@ fun MainPostListingsContent( swipeToActionPreset = swipeToActionPreset, disableVideoAutoplay = disableVideoAutoplay, lowBandwidthMode = lowBandwidthMode, - enableInfiniteScroll = enableInfiniteScroll + enableInfiniteScroll = enableInfiniteScroll, + onPreviousPage = { }, + onNextPage = homeViewModel::nextPage, + currentPage = 1 ) } } diff --git a/app/src/main/java/com/jerboa/ui/components/person/PersonProfileScreen.kt b/app/src/main/java/com/jerboa/ui/components/person/PersonProfileScreen.kt index a72b91930..1c9543dbd 100644 --- a/app/src/main/java/com/jerboa/ui/components/person/PersonProfileScreen.kt +++ b/app/src/main/java/com/jerboa/ui/components/person/PersonProfileScreen.kt @@ -684,7 +684,10 @@ fun UserTabs( swipeToActionPreset = swipeToActionPreset, disableVideoAutoplay = disableVideoAutoplay, lowBandwidthMode = lowBandwidthMode, - enableInfiniteScroll = enableInfiniteScroll + enableInfiniteScroll = enableInfiniteScroll, + onNextPage = { }, + onPreviousPage = { }, + currentPage = 1, ) } diff --git a/app/src/main/java/com/jerboa/ui/components/post/PostListings.kt b/app/src/main/java/com/jerboa/ui/components/post/PostListings.kt index b9fe029c2..c5682a99a 100644 --- a/app/src/main/java/com/jerboa/ui/components/post/PostListings.kt +++ b/app/src/main/java/com/jerboa/ui/components/post/PostListings.kt @@ -65,6 +65,9 @@ fun PostListings( onCommunityClick: (community: Community) -> Unit, onPersonClick: (personId: PersonId) -> Unit, loadMorePosts: () -> Unit, + onNextPage: () -> Unit, + onPreviousPage: () -> Unit, + currentPage: Long, account: Account, showCommunityName: Boolean = true, listState: LazyListState, @@ -168,15 +171,15 @@ fun PostListings( item(contentType = "pagination_buttons") { Spacer(Modifier.padding(vertical = SMALL_PADDING)) PaginationButton( - currentPage = 1, // TODO: pass correct value - onNext = { }, - onPrevious = { } + currentPage = currentPage, // TODO: current page not updating properly + onNext = onNextPage, + onPrevious = onPreviousPage ) } } } - TriggerWhenReachingEnd(listState, showPostAppendRetry, loadMorePosts) + if (enableInfiniteScroll) TriggerWhenReachingEnd(listState, showPostAppendRetry, loadMorePosts) } @Preview(showBackground = true) @@ -224,6 +227,9 @@ fun PreviewPostListings() { onReplyClick = {}, disableVideoAutoplay = false, lowBandwidthMode = false, - enableInfiniteScroll = false + enableInfiniteScroll = false, + onNextPage = { }, + onPreviousPage = { }, + currentPage = 1 ) } From adca5f5a18c7f402e3e191648cb559df60459bd5 Mon Sep 17 00:00:00 2001 From: rodrigo-fm Date: Mon, 6 Jul 2026 20:31:29 -0300 Subject: [PATCH 04/22] [pagination-scroll] - adds functionality to the previous page button. --- .../com/jerboa/feed/PaginationController.kt | 31 +++++++++++---- .../java/com/jerboa/model/PostsViewModel.kt | 38 ++++++++++++++++--- .../jerboa/ui/components/home/HomeActivity.kt | 2 +- .../jerboa/ui/components/post/PostListings.kt | 4 +- 4 files changed, 58 insertions(+), 17 deletions(-) diff --git a/app/src/main/java/com/jerboa/feed/PaginationController.kt b/app/src/main/java/com/jerboa/feed/PaginationController.kt index 4756e3059..f84b25986 100644 --- a/app/src/main/java/com/jerboa/feed/PaginationController.kt +++ b/app/src/main/java/com/jerboa/feed/PaginationController.kt @@ -1,18 +1,33 @@ package com.jerboa.feed import it.vercruysse.lemmyapi.datatypes.PaginationCursor +import java.util.Stack + +class PaginationController { + + val previousPageCursors = Stack() + var currentPageCursor: PaginationCursor? = null + var nextPageCursor: PaginationCursor? = null + var page: Long = 1 + + fun appendPage(nextPage: PaginationCursor?) { + page++ + this.nextPageCursor = nextPage + } -class PaginationController( - var page: Long = 1, - var pageCursor: PaginationCursor? = null, -) { fun reset() { page = 1 - pageCursor = null + currentPageCursor = null + nextPageCursor = null + previousPageCursors.clear() } - fun nextPage(pageCursor: PaginationCursor?) { + fun nextPage(nextPage: PaginationCursor?) { page++ - this.pageCursor = pageCursor + previousPageCursors.push(this.currentPageCursor) + this.currentPageCursor = this.nextPageCursor + this.nextPageCursor = nextPage } -} + + fun canMoveToPreviousPage(): Boolean = page > 1 && previousPageCursors.isNotEmpty() +} \ No newline at end of file diff --git a/app/src/main/java/com/jerboa/model/PostsViewModel.kt b/app/src/main/java/com/jerboa/model/PostsViewModel.kt index 5264f2a78..1a5dcccf7 100644 --- a/app/src/main/java/com/jerboa/model/PostsViewModel.kt +++ b/app/src/main/java/com/jerboa/model/PostsViewModel.kt @@ -28,6 +28,7 @@ import it.vercruysse.lemmyapi.datatypes.GetPosts import it.vercruysse.lemmyapi.datatypes.HidePost import it.vercruysse.lemmyapi.datatypes.LockPost import it.vercruysse.lemmyapi.datatypes.MarkPostAsRead +import it.vercruysse.lemmyapi.datatypes.PaginationCursor import it.vercruysse.lemmyapi.datatypes.PersonView import it.vercruysse.lemmyapi.datatypes.PostView import it.vercruysse.lemmyapi.datatypes.SavePost @@ -67,12 +68,14 @@ open class PostsViewModel( private fun initPosts( form: GetPosts, state: ApiState> = ApiState.Loading, + onSuccess: (PaginationCursor?) -> Unit = {}, ) { + postController.clear() viewModelScope.launch { postsRes = state postsRes = API.getInstance().getPosts(form).fold( onSuccess = { - pageController.nextPage(it.next_page) + onSuccess(it.next_page) postController.addAll(it.posts) ApiState.Success(postController.feed) }, @@ -93,7 +96,7 @@ open class PostsViewModel( when (val newRes = API.getInstance().getPosts(getForm()).toApiState()) { is ApiState.Success -> { - pageController.nextPage(newRes.data.next_page) + pageController.appendPage(newRes.data.next_page) postController.addAll(newRes.data.posts) postsRes = ApiState.Success(oldRes.data) } @@ -114,25 +117,48 @@ open class PostsViewModel( } fun nextPage() { - postController.clear() - initPosts(getForm(), ApiState.Loading) + initPosts( + getForm(), + ApiState.Loading, + onSuccess = pageController::nextPage + ) + } + + fun previousPage() { + if (!pageController.canMoveToPreviousPage()) return + + pageController.page-- + pageController.currentPageCursor = pageController.previousPageCursors.pop() + + initPosts( + getPreviousPageForm(), + ApiState.Loading, + onSuccess = { pageController.nextPageCursor = it }, + ) } fun resetPosts(state: ApiState> = ApiState.Loading) { pageController.reset() - postController.clear() initPosts( getForm(), state, + onSuccess = { pageController.nextPageCursor = it } ) } fun refreshPosts() = resetPosts(ApiState.Refreshing) + private fun getPreviousPageForm() = GetPosts( + page = pageController.page, + page_cursor = pageController.currentPageCursor, + sort = sortType, + type_ = listingType, + ) + protected open fun getForm(): GetPosts = GetPosts( page = pageController.page, - page_cursor = pageController.pageCursor, + page_cursor = pageController.nextPageCursor, sort = sortType, type_ = listingType, ) diff --git a/app/src/main/java/com/jerboa/ui/components/home/HomeActivity.kt b/app/src/main/java/com/jerboa/ui/components/home/HomeActivity.kt index 6443cb074..3fa2b115d 100644 --- a/app/src/main/java/com/jerboa/ui/components/home/HomeActivity.kt +++ b/app/src/main/java/com/jerboa/ui/components/home/HomeActivity.kt @@ -475,7 +475,7 @@ fun MainPostListingsContent( disableVideoAutoplay = disableVideoAutoplay, lowBandwidthMode = lowBandwidthMode, enableInfiniteScroll = enableInfiniteScroll, - onPreviousPage = { }, + onPreviousPage = homeViewModel::previousPage, onNextPage = homeViewModel::nextPage, currentPage = 1 ) diff --git a/app/src/main/java/com/jerboa/ui/components/post/PostListings.kt b/app/src/main/java/com/jerboa/ui/components/post/PostListings.kt index c5682a99a..24e7eb422 100644 --- a/app/src/main/java/com/jerboa/ui/components/post/PostListings.kt +++ b/app/src/main/java/com/jerboa/ui/components/post/PostListings.kt @@ -161,13 +161,13 @@ fun PostListings( ) } - if (showPostAppendRetry) { + if (enableInfiniteScroll && showPostAppendRetry) { item(contentType = "retry_posts") { RetryLoadingPosts(loadMorePosts) } } - if (!enableInfiniteScroll) { + if (!enableInfiniteScroll && posts.isNotEmpty()) { item(contentType = "pagination_buttons") { Spacer(Modifier.padding(vertical = SMALL_PADDING)) PaginationButton( From 974a48144564fa32313c4b3a2f38ba628a35bfb0 Mon Sep 17 00:00:00 2001 From: rodrigo-fm Date: Tue, 7 Jul 2026 21:31:48 -0300 Subject: [PATCH 05/22] [pagination-scroll] - hides floating action button when reaching the end with enableInfiniteScroll disabled. --- .../com/jerboa/feed/PaginationController.kt | 5 +- .../java/com/jerboa/model/PostsViewModel.kt | 3 + .../jerboa/ui/components/common/Buttons.kt | 2 +- .../components/community/CommunityScreen.kt | 60 +++++++++++-------- .../jerboa/ui/components/home/HomeActivity.kt | 56 ++++++++++------- .../jerboa/ui/components/post/PostListings.kt | 2 +- 6 files changed, 77 insertions(+), 51 deletions(-) diff --git a/app/src/main/java/com/jerboa/feed/PaginationController.kt b/app/src/main/java/com/jerboa/feed/PaginationController.kt index f84b25986..1a4170992 100644 --- a/app/src/main/java/com/jerboa/feed/PaginationController.kt +++ b/app/src/main/java/com/jerboa/feed/PaginationController.kt @@ -1,5 +1,8 @@ package com.jerboa.feed +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableLongStateOf +import androidx.compose.runtime.setValue import it.vercruysse.lemmyapi.datatypes.PaginationCursor import java.util.Stack @@ -8,7 +11,7 @@ class PaginationController { val previousPageCursors = Stack() var currentPageCursor: PaginationCursor? = null var nextPageCursor: PaginationCursor? = null - var page: Long = 1 + var page by mutableLongStateOf(1) fun appendPage(nextPage: PaginationCursor?) { page++ diff --git a/app/src/main/java/com/jerboa/model/PostsViewModel.kt b/app/src/main/java/com/jerboa/model/PostsViewModel.kt index 1a5dcccf7..db9b23b7c 100644 --- a/app/src/main/java/com/jerboa/model/PostsViewModel.kt +++ b/app/src/main/java/com/jerboa/model/PostsViewModel.kt @@ -51,6 +51,9 @@ open class PostsViewModel( private val pageController = PaginationController() private val postController = PostController() + val currentPage: Long + get() = pageController.page + protected fun init() { viewModelScope.launch { accountRepository.currentAccount diff --git a/app/src/main/java/com/jerboa/ui/components/common/Buttons.kt b/app/src/main/java/com/jerboa/ui/components/common/Buttons.kt index bdb1a9370..e643e0e76 100644 --- a/app/src/main/java/com/jerboa/ui/components/common/Buttons.kt +++ b/app/src/main/java/com/jerboa/ui/components/common/Buttons.kt @@ -47,7 +47,7 @@ fun PaginationButton( horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically ) { - Button(onClick = onPrevious) { + Button(onClick = onPrevious, enabled = currentPage > 1) { Text(stringResource(R.string.previous)) } Text(stringResource(R.string.page_number, currentPage)) diff --git a/app/src/main/java/com/jerboa/ui/components/community/CommunityScreen.kt b/app/src/main/java/com/jerboa/ui/components/community/CommunityScreen.kt index 2e47fd7d6..33037a781 100644 --- a/app/src/main/java/com/jerboa/ui/components/community/CommunityScreen.kt +++ b/app/src/main/java/com/jerboa/ui/components/community/CommunityScreen.kt @@ -1,6 +1,7 @@ package com.jerboa.ui.components.community import android.util.Log +import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons @@ -15,6 +16,8 @@ import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.pulltorefresh.PullToRefreshBox import androidx.compose.material3.rememberTopAppBarState import androidx.compose.runtime.Composable +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier @@ -38,6 +41,7 @@ import com.jerboa.feat.doIfReadyElseDisplayInfo import com.jerboa.feat.newVote import com.jerboa.feat.shareLink import com.jerboa.hostName +import com.jerboa.isScrolledToEnd import com.jerboa.model.AccountViewModel import com.jerboa.model.AppSettingsViewModel import com.jerboa.model.CommunityViewModel @@ -105,6 +109,10 @@ fun CommunityScreen( viewModel(factory = CommunityViewModel.Companion.Factory(communityArg)) val postListState = communityViewModel.lazyListState + val hideFab by remember { + derivedStateOf { postListState.isScrolledToEnd() && !enableInfiniteScroll } + } + appState.ConsumeReturn(PostEditReturn.POST_VIEW, communityViewModel::updatePost) appState.ConsumeReturn(PostRemoveReturn.POST_VIEW, communityViewModel::updatePost) appState.ConsumeReturn(PostViewReturn.POST_VIEW, communityViewModel::updatePost) @@ -459,9 +467,9 @@ fun CommunityScreen( disableVideoAutoplay = disableVideoAutoplay, lowBandwidthMode = lowBandwidthMode, enableInfiniteScroll = enableInfiniteScroll, - onNextPage = { }, - onPreviousPage = { }, - currentPage = 1 + onNextPage = communityViewModel::nextPage, + onPreviousPage = communityViewModel::previousPage, + currentPage = communityViewModel.currentPage, ) } @@ -473,28 +481,30 @@ fun CommunityScreen( floatingActionButton = { when (val communityRes = communityViewModel.communityRes) { is ApiState.Success -> { - FloatingActionButton( - onClick = { - account.doIfReadyElseDisplayInfo( - appState, - ctx, - resources, - snackbarHostState, - scope, - siteViewModel, - accountViewModel, - loginAsToast = false, - ) { - appState.toCreatePost( - community = communityRes.data.community_view.community, - ) - } - }, - ) { - Icon( - imageVector = Icons.Outlined.Add, - contentDescription = stringResource(R.string.floating_createPost), - ) + AnimatedVisibility(visible = !hideFab) { + FloatingActionButton( + onClick = { + account.doIfReadyElseDisplayInfo( + appState, + ctx, + resources, + snackbarHostState, + scope, + siteViewModel, + accountViewModel, + loginAsToast = false, + ) { + appState.toCreatePost( + community = communityRes.data.community_view.community, + ) + } + }, + ) { + Icon( + imageVector = Icons.Outlined.Add, + contentDescription = stringResource(R.string.floating_createPost), + ) + } } } diff --git a/app/src/main/java/com/jerboa/ui/components/home/HomeActivity.kt b/app/src/main/java/com/jerboa/ui/components/home/HomeActivity.kt index 3fa2b115d..42fae3e2a 100644 --- a/app/src/main/java/com/jerboa/ui/components/home/HomeActivity.kt +++ b/app/src/main/java/com/jerboa/ui/components/home/HomeActivity.kt @@ -2,6 +2,7 @@ package com.jerboa.ui.components.home import android.util.Log import androidx.activity.compose.ReportDrawn +import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues @@ -24,6 +25,8 @@ import androidx.compose.material3.pulltorefresh.PullToRefreshBox import androidx.compose.material3.rememberTopAppBarState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.ExperimentalComposeUiApi @@ -47,6 +50,7 @@ import com.jerboa.feat.SwipeToActionPreset import com.jerboa.feat.VoteType import com.jerboa.feat.doIfReadyElseDisplayInfo import com.jerboa.feat.newVote +import com.jerboa.isScrolledToEnd import com.jerboa.model.AccountViewModel import com.jerboa.model.AppSettingsViewModel import com.jerboa.model.HomeViewModel @@ -116,6 +120,10 @@ fun HomeScreen( // Forget snackbars of previous accounts val snackbarHostState = remember(account) { SnackbarHostState() } + val hideFab by remember { + derivedStateOf { postListState.isScrolledToEnd() && !enableInfiniteScroll } + } + appState.ConsumeReturn(PostEditReturn.POST_VIEW, homeViewModel::updatePost) appState.ConsumeReturn(PostRemoveReturn.POST_VIEW, homeViewModel::updatePost) appState.ConsumeReturn(PostViewReturn.POST_VIEW, homeViewModel::updatePost) @@ -190,28 +198,30 @@ fun HomeScreen( }, floatingActionButtonPosition = FabPosition.End, floatingActionButton = { - FloatingActionButton( - onClick = { - account.doIfReadyElseDisplayInfo( - appState, - ctx, - resources, - snackbarHostState, - scope, - siteViewModel, - accountViewModel, - loginAsToast = false, - ) { - appState.toCreatePost( - community = null, - ) - } - }, - ) { - Icon( - imageVector = Icons.Outlined.Add, - contentDescription = stringResource(R.string.floating_createPost), - ) + AnimatedVisibility(visible = !hideFab) { + FloatingActionButton( + onClick = { + account.doIfReadyElseDisplayInfo( + appState, + ctx, + resources, + snackbarHostState, + scope, + siteViewModel, + accountViewModel, + loginAsToast = false, + ) { + appState.toCreatePost( + community = null, + ) + } + }, + ) { + Icon( + imageVector = Icons.Outlined.Add, + contentDescription = stringResource(R.string.floating_createPost), + ) + } } }, ) @@ -477,7 +487,7 @@ fun MainPostListingsContent( enableInfiniteScroll = enableInfiniteScroll, onPreviousPage = homeViewModel::previousPage, onNextPage = homeViewModel::nextPage, - currentPage = 1 + currentPage = homeViewModel.currentPage, ) } } diff --git a/app/src/main/java/com/jerboa/ui/components/post/PostListings.kt b/app/src/main/java/com/jerboa/ui/components/post/PostListings.kt index 24e7eb422..ac73603fe 100644 --- a/app/src/main/java/com/jerboa/ui/components/post/PostListings.kt +++ b/app/src/main/java/com/jerboa/ui/components/post/PostListings.kt @@ -171,7 +171,7 @@ fun PostListings( item(contentType = "pagination_buttons") { Spacer(Modifier.padding(vertical = SMALL_PADDING)) PaginationButton( - currentPage = currentPage, // TODO: current page not updating properly + currentPage = currentPage, onNext = onNextPage, onPrevious = onPreviousPage ) From f0d1f0febda889edf1930ca0c2b0cdb2ab9727b0 Mon Sep 17 00:00:00 2001 From: rodrigo-fm Date: Wed, 8 Jul 2026 19:51:48 -0300 Subject: [PATCH 06/22] [pagination-scroll] - adds pagination to a user's posts. --- .../jerboa/model/PersonProfileViewModel.kt | 34 ++++++++++++------- .../components/person/PersonProfileScreen.kt | 15 ++++---- .../jerboa/ui/components/post/PostListings.kt | 1 - 3 files changed, 30 insertions(+), 20 deletions(-) diff --git a/app/src/main/java/com/jerboa/model/PersonProfileViewModel.kt b/app/src/main/java/com/jerboa/model/PersonProfileViewModel.kt index 541c33187..64e4129d2 100644 --- a/app/src/main/java/com/jerboa/model/PersonProfileViewModel.kt +++ b/app/src/main/java/com/jerboa/model/PersonProfileViewModel.kt @@ -44,7 +44,6 @@ class PersonProfileViewModel( private var hidePostRes: ApiState<(Unit)> by mutableStateOf(ApiState.Empty) private var lockPostRes: ApiState by mutableStateOf(ApiState.Empty) private var featurePostRes: ApiState by mutableStateOf(ApiState.Empty) - private var blockCommunityRes: ApiState by mutableStateOf(ApiState.Empty) private var blockPersonRes: ApiState by mutableStateOf(ApiState.Empty) private var likeCommentRes: ApiState by mutableStateOf(ApiState.Empty) @@ -126,6 +125,27 @@ class PersonProfileViewModel( } } + fun navigatePagination(profileId: PersonId, pageIncrement: Long) { + viewModelScope.launch { + personDetailsRes = ApiState.Loading + + page += pageIncrement + val form = GetPersonDetails( + person_id = profileId, + sort = sortType, + page = page, + saved_only = savedOnly, + ) + val newRes = API.getInstance().getPersonDetails(form).toApiState() + + if (newRes is ApiState.Success) { + personDetailsRes = ApiState.Success(newRes.data) + } else { + page -= pageIncrement + } + } + } + fun appendData(profileId: PersonId) { viewModelScope.launch { val oldRes = personDetailsRes @@ -262,18 +282,6 @@ class PersonProfileViewModel( } } - fun blockCommunity( - form: BlockCommunity, - ctx: Context, - ) { - viewModelScope.launch { - blockCommunityRes = ApiState.Loading - val res = API.getInstance().blockCommunity(form) - blockCommunityRes = res.toApiState() - showBlockCommunityToast(res, ctx) - } - } - fun blockPerson( form: BlockPerson, ctx: Context, diff --git a/app/src/main/java/com/jerboa/ui/components/person/PersonProfileScreen.kt b/app/src/main/java/com/jerboa/ui/components/person/PersonProfileScreen.kt index 1c9543dbd..7f7f25fd8 100644 --- a/app/src/main/java/com/jerboa/ui/components/person/PersonProfileScreen.kt +++ b/app/src/main/java/com/jerboa/ui/components/person/PersonProfileScreen.kt @@ -485,6 +485,7 @@ fun UserTabs( } is ApiState.Holder -> { + val profileId = profileRes.data.person_view.person.id PostListings( posts = profileRes.data.posts.toList(), admins = siteViewModel.admins(), @@ -649,9 +650,7 @@ fun UserTabs( }, onPersonClick = appState::toProfile, loadMorePosts = { - personProfileViewModel.appendData( - profileRes.data.person_view.person.id, - ) + personProfileViewModel.appendData(profileId) }, account = account, listState = postListState, @@ -685,9 +684,13 @@ fun UserTabs( disableVideoAutoplay = disableVideoAutoplay, lowBandwidthMode = lowBandwidthMode, enableInfiniteScroll = enableInfiniteScroll, - onNextPage = { }, - onPreviousPage = { }, - currentPage = 1, + onNextPage = { + personProfileViewModel.navigatePagination(profileId, 1) + }, + onPreviousPage = { + personProfileViewModel.navigatePagination(profileId, -1) + }, + currentPage = personProfileViewModel.page, ) } diff --git a/app/src/main/java/com/jerboa/ui/components/post/PostListings.kt b/app/src/main/java/com/jerboa/ui/components/post/PostListings.kt index ac73603fe..f6d34e025 100644 --- a/app/src/main/java/com/jerboa/ui/components/post/PostListings.kt +++ b/app/src/main/java/com/jerboa/ui/components/post/PostListings.kt @@ -169,7 +169,6 @@ fun PostListings( if (!enableInfiniteScroll && posts.isNotEmpty()) { item(contentType = "pagination_buttons") { - Spacer(Modifier.padding(vertical = SMALL_PADDING)) PaginationButton( currentPage = currentPage, onNext = onNextPage, From 80b0f5d294afd1448449210f14bcc3337d628fef Mon Sep 17 00:00:00 2001 From: rodrigo-fm Date: Wed, 8 Jul 2026 22:17:36 -0300 Subject: [PATCH 07/22] [pagination-scroll] - adds pagination to a user's comments. --- .../ui/components/comment/CommentNode.kt | 4 + .../ui/components/comment/CommentNodes.kt | 142 ++++++++++++------ .../jerboa/ui/components/common/Buttons.kt | 3 +- .../components/person/PersonProfileScreen.kt | 13 +- app/src/main/res/values/strings.xml | 1 + 5 files changed, 112 insertions(+), 51 deletions(-) diff --git a/app/src/main/java/com/jerboa/ui/components/comment/CommentNode.kt b/app/src/main/java/com/jerboa/ui/components/comment/CommentNode.kt index b73319cce..ea92fb83f 100644 --- a/app/src/main/java/com/jerboa/ui/components/comment/CommentNode.kt +++ b/app/src/main/java/com/jerboa/ui/components/comment/CommentNode.kt @@ -941,6 +941,10 @@ fun CommentNodesPreview() { account = AnonAccount, voteDisplayMode = LocalUserVoteDisplayMode.default(), swipeToActionPreset = SwipeToActionPreset.TwoSides, + nextPage = { }, + previousPage = { }, + page = 2, + enableInfiniteScroll = false, ) } diff --git a/app/src/main/java/com/jerboa/ui/components/comment/CommentNodes.kt b/app/src/main/java/com/jerboa/ui/components/comment/CommentNodes.kt index 374ef609c..d008c7677 100644 --- a/app/src/main/java/com/jerboa/ui/components/comment/CommentNodes.kt +++ b/app/src/main/java/com/jerboa/ui/components/comment/CommentNodes.kt @@ -1,20 +1,29 @@ package com.jerboa.ui.components.comment +import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyListScope import androidx.compose.foundation.lazy.LazyListState +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import com.jerboa.CommentNode import com.jerboa.CommentNodeData import com.jerboa.MissingCommentNode +import com.jerboa.R import com.jerboa.datatypes.BanFromCommunityData import com.jerboa.db.entity.Account import com.jerboa.feat.BlurNSFW import com.jerboa.feat.SwipeToActionPreset +import com.jerboa.ui.components.common.PaginationButton import it.vercruysse.lemmyapi.datatypes.CommentId import it.vercruysse.lemmyapi.datatypes.CommentView import it.vercruysse.lemmyapi.datatypes.Community @@ -68,57 +77,98 @@ fun CommentNodes( blurNSFW: BlurNSFW, voteDisplayMode: LocalUserVoteDisplayMode, swipeToActionPreset: SwipeToActionPreset, + enableInfiniteScroll: Boolean, + nextPage: () -> Unit, + previousPage: () -> Unit, + page: Long ) { - LazyColumn(state = listState) { - commentNodeItems( - nodes = nodes, - admins = admins, - moderators = moderators, - increaseLazyListIndexTracker = increaseLazyListIndexTracker, - addToParentIndexes = addToParentIndexes, - isFlat = isFlat, - isExpanded = isExpanded, - toggleExpanded = toggleExpanded, - toggleActionBar = toggleActionBar, - onUpvoteClick = onUpvoteClick, - onDownvoteClick = onDownvoteClick, - onReplyClick = onReplyClick, - onSaveClick = onSaveClick, - onMarkAsReadClick = onMarkAsReadClick, - onCommentClick = onCommentClick, - onEditCommentClick = onEditCommentClick, - onDeleteCommentClick = onDeleteCommentClick, - onReportClick = onReportClick, - onRemoveClick = onRemoveClick, - onDistinguishClick = onDistinguishClick, - onBanPersonClick = onBanPersonClick, - onBanFromCommunityClick = onBanFromCommunityClick, - onCommentLinkClick = onCommentLinkClick, - onFetchChildrenClick = onFetchChildrenClick, - onPersonClick = onPersonClick, - onViewVotesClick = onViewVotesClick, - onHeaderClick = onHeaderClick, - onHeaderLongClick = onHeaderLongClick, - onCommunityClick = onCommunityClick, - onBlockCreatorClick = onBlockCreatorClick, - onPostClick = onPostClick, - account = account, - showPostAndCommunityContext = showPostAndCommunityContext, - showCollapsedCommentContent = showCollapsedCommentContent, - isCollapsedByParent = isCollapsedByParent, - showActionBar = showActionBar, - enableDownVotes = enableDownVotes, - showAvatar = showAvatar, - blurNSFW = blurNSFW, - voteDisplayMode = voteDisplayMode, - swipeToActionPreset = swipeToActionPreset, - ) - item { - Spacer(modifier = Modifier.height(100.dp)) + if (nodes.isEmpty() && !enableInfiniteScroll) { + NoMoreComments(page, previousPage) + } else { + LazyColumn(state = listState) { + commentNodeItems( + nodes = nodes, + admins = admins, + moderators = moderators, + increaseLazyListIndexTracker = increaseLazyListIndexTracker, + addToParentIndexes = addToParentIndexes, + isFlat = isFlat, + isExpanded = isExpanded, + toggleExpanded = toggleExpanded, + toggleActionBar = toggleActionBar, + onUpvoteClick = onUpvoteClick, + onDownvoteClick = onDownvoteClick, + onReplyClick = onReplyClick, + onSaveClick = onSaveClick, + onMarkAsReadClick = onMarkAsReadClick, + onCommentClick = onCommentClick, + onEditCommentClick = onEditCommentClick, + onDeleteCommentClick = onDeleteCommentClick, + onReportClick = onReportClick, + onRemoveClick = onRemoveClick, + onDistinguishClick = onDistinguishClick, + onBanPersonClick = onBanPersonClick, + onBanFromCommunityClick = onBanFromCommunityClick, + onCommentLinkClick = onCommentLinkClick, + onFetchChildrenClick = onFetchChildrenClick, + onPersonClick = onPersonClick, + onViewVotesClick = onViewVotesClick, + onHeaderClick = onHeaderClick, + onHeaderLongClick = onHeaderLongClick, + onCommunityClick = onCommunityClick, + onBlockCreatorClick = onBlockCreatorClick, + onPostClick = onPostClick, + account = account, + showPostAndCommunityContext = showPostAndCommunityContext, + showCollapsedCommentContent = showCollapsedCommentContent, + isCollapsedByParent = isCollapsedByParent, + showActionBar = showActionBar, + enableDownVotes = enableDownVotes, + showAvatar = showAvatar, + blurNSFW = blurNSFW, + voteDisplayMode = voteDisplayMode, + swipeToActionPreset = swipeToActionPreset, + ) + item { + if (enableInfiniteScroll) Spacer(modifier = Modifier.height(100.dp)) + } + item { + if (!enableInfiniteScroll) { + PaginationButton( + currentPage = page, + onNext = nextPage, + onPrevious = previousPage, + onNextEnabled = nodes.isNotEmpty() + ) + } + } } } } +@Composable +private fun NoMoreComments( + page: Long, + previousPage: () -> Unit, +) { + Column(modifier = Modifier.fillMaxSize()) { + Text( + modifier = Modifier + .weight(1f) + .fillMaxWidth() + .wrapContentHeight(), + textAlign = TextAlign.Center, + text = stringResource(R.string.no_more_comments) + ) + PaginationButton( + currentPage = page, + onNext = { }, + onPrevious = previousPage, + onNextEnabled = false + ) + } +} + fun LazyListScope.commentNodeItems( nodes: List, admins: List, diff --git a/app/src/main/java/com/jerboa/ui/components/common/Buttons.kt b/app/src/main/java/com/jerboa/ui/components/common/Buttons.kt index e643e0e76..bf4a980ef 100644 --- a/app/src/main/java/com/jerboa/ui/components/common/Buttons.kt +++ b/app/src/main/java/com/jerboa/ui/components/common/Buttons.kt @@ -39,6 +39,7 @@ fun PaginationButton( currentPage: Long, onNext: () -> Unit, onPrevious: () -> Unit, + onNextEnabled: Boolean = true ) { Row( modifier = Modifier @@ -51,7 +52,7 @@ fun PaginationButton( Text(stringResource(R.string.previous)) } Text(stringResource(R.string.page_number, currentPage)) - Button(onClick = onNext) { + Button(onClick = onNext, enabled = onNextEnabled) { Text(stringResource(R.string.next)) } } diff --git a/app/src/main/java/com/jerboa/ui/components/person/PersonProfileScreen.kt b/app/src/main/java/com/jerboa/ui/components/person/PersonProfileScreen.kt index 7f7f25fd8..541d02408 100644 --- a/app/src/main/java/com/jerboa/ui/components/person/PersonProfileScreen.kt +++ b/app/src/main/java/com/jerboa/ui/components/person/PersonProfileScreen.kt @@ -716,13 +716,14 @@ fun UserTabs( is ApiState.Holder -> { val nodes = commentsToFlatNodes(profileRes.data.comments) + val profileId = profileRes.data.person_view.person.id val listState = rememberLazyListState() - TriggerWhenReachingEnd(listState, false) { - personProfileViewModel.appendData( - profileRes.data.person_view.person.id, - ) + if (enableInfiniteScroll) { + TriggerWhenReachingEnd(listState, false) { + personProfileViewModel.appendData(profileId) + } } // Holds the un-expanded comment ids @@ -918,6 +919,10 @@ fun UserTabs( blurNSFW = blurNSFW, voteDisplayMode = voteDisplayMode, swipeToActionPreset = swipeToActionPreset, + enableInfiniteScroll = enableInfiniteScroll, + nextPage = { personProfileViewModel.navigatePagination(profileId, 1) }, + previousPage = { personProfileViewModel.navigatePagination(profileId, -1) }, + page = personProfileViewModel.page ) } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 7b1a10a5f..76324bd9e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -513,4 +513,5 @@ Page %1$d Previous Next + No more comments available From 95e3c35e01b34685b9c2c5657b2c50c12858e464 Mon Sep 17 00:00:00 2001 From: Dessalines Date: Fri, 3 Jul 2026 10:23:18 -0400 Subject: [PATCH 08/22] Changing renovate to monthly (#2161) --- renovate.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/renovate.json b/renovate.json index b3eaa8e97..d2c5e43a1 100644 --- a/renovate.json +++ b/renovate.json @@ -1,6 +1,6 @@ { "$schema": "https://docs.renovatebot.com/renovate-schema.json", "extends": ["config:recommended"], - "schedule": ["every weekend"], + "schedule": ["* * 1 * *"], "automerge": true } From c75a1d53a689791df7e4c37ac7690b61323dc0b7 Mon Sep 17 00:00:00 2001 From: Salif Mehmed Date: Sun, 5 Jul 2026 11:26:26 +0000 Subject: [PATCH 09/22] Translated using Weblate (Bulgarian) Currently translated at 100.0% (510 of 510 strings) Translation: Lemmy/jerboa Translate-URL: http://weblate.join-lemmy.org/projects/lemmy/jerboa/bg/ --- app/src/main/res/values-bg/strings.xml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/values-bg/strings.xml b/app/src/main/res/values-bg/strings.xml index 0160c1d54..f4549388e 100644 --- a/app/src/main/res/values-bg/strings.xml +++ b/app/src/main/res/values-bg/strings.xml @@ -502,4 +502,8 @@ Базата данни е архивирана Пускане Базов профил - \ No newline at end of file + Режим на малко данни + Автоматично (Икономия на данни) + Винаги включено + Изключено + From 588638d18e595e4e183a6263629e5437e0977604 Mon Sep 17 00:00:00 2001 From: rodrigo-fm Date: Thu, 9 Jul 2026 23:28:18 -0300 Subject: [PATCH 10/22] [pagination-scroll] - adds a "no more posts available" string to pagination. --- .../jerboa/ui/components/home/HomeActivity.kt | 4 +- .../jerboa/ui/components/post/PostListings.kt | 133 ++++++++++-------- app/src/main/res/values/strings.xml | 1 + 3 files changed, 78 insertions(+), 60 deletions(-) diff --git a/app/src/main/java/com/jerboa/ui/components/home/HomeActivity.kt b/app/src/main/java/com/jerboa/ui/components/home/HomeActivity.kt index 42fae3e2a..cbe2574e4 100644 --- a/app/src/main/java/com/jerboa/ui/components/home/HomeActivity.kt +++ b/app/src/main/java/com/jerboa/ui/components/home/HomeActivity.kt @@ -278,7 +278,7 @@ fun MainPostListingsContent( ) { JerboaLoadingBar(homeViewModel.postsRes) - val posts: List = when (val postsRes = homeViewModel.postsRes) { + val posts: List? = when (val postsRes = homeViewModel.postsRes) { is ApiState.Failure -> { apiErrorToast(ctx, postsRes.msg) listOf() @@ -289,7 +289,7 @@ fun MainPostListingsContent( } else -> { - listOf() + null } } diff --git a/app/src/main/java/com/jerboa/ui/components/post/PostListings.kt b/app/src/main/java/com/jerboa/ui/components/post/PostListings.kt index f6d34e025..35603fead 100644 --- a/app/src/main/java/com/jerboa/ui/components/post/PostListings.kt +++ b/app/src/main/java/com/jerboa/ui/components/post/PostListings.kt @@ -1,8 +1,8 @@ package com.jerboa.ui.components.post import androidx.compose.foundation.layout.ExperimentalLayoutApi -import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyListState @@ -10,13 +10,17 @@ import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.ui.Modifier import androidx.compose.ui.platform.testTag +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import com.jerboa.JerboaAppState import com.jerboa.PostViewMode +import com.jerboa.R import com.jerboa.datatypes.BanFromCommunityData import com.jerboa.datatypes.PostFeatureData import com.jerboa.datatypes.sampleLinkPostView @@ -32,6 +36,7 @@ import com.jerboa.ui.components.common.PaginationButton import com.jerboa.ui.components.common.RetryLoadingPosts import com.jerboa.ui.components.common.TriggerWhenReachingEnd import com.jerboa.ui.theme.SMALL_PADDING +import com.jerboa.ui.theme.XL_PADDING import it.vercruysse.lemmyapi.datatypes.Community import it.vercruysse.lemmyapi.datatypes.LocalUserVoteDisplayMode import it.vercruysse.lemmyapi.datatypes.Person @@ -43,7 +48,7 @@ import it.vercruysse.lemmyapi.datatypes.PostView @OptIn(ExperimentalLayoutApi::class) @Composable fun PostListings( - posts: List, + posts: List?, admins: List, moderators: List?, contentAboveListings: @Composable () -> Unit = {}, @@ -100,65 +105,76 @@ fun PostListings( item(contentType = "aboveContent") { contentAboveListings() } - // List of items - itemsIndexed( - items = posts, - contentType = { _, _ -> "Post" }, - ) { index, postView -> - PostListing( - postView = postView, - admins = admins, - moderators = moderators, - useCustomTabs = useCustomTabs, - usePrivateTabs = usePrivateTabs, - onUpvoteClick = onUpvoteClick, - onDownvoteClick = onDownvoteClick, - onReplyClick = onReplyClick, - onPostClick = onPostClick, - onSaveClick = onSaveClick, - onCommunityClick = onCommunityClick, - onEditPostClick = onEditPostClick, - onDeletePostClick = onDeletePostClick, - onHidePostClick = onHidePostClick, - onReportClick = onReportClick, - onRemoveClick = onRemoveClick, - onBanPersonClick = onBanPersonClick, - onBanFromCommunityClick = onBanFromCommunityClick, - onLockPostClick = onLockPostClick, - onFeaturePostClick = onFeaturePostClick, - onViewVotesClick = onViewPostVotesClick, - onPersonClick = onPersonClick, - showCommunityName = showCommunityName, - fullBody = false, - account = account, - postViewMode = postViewMode, - showVotingArrowsInListView = showVotingArrowsInListView, - enableDownVotes = enableDownVotes, - showAvatar = showAvatar, - blurNSFW = blurNSFW, - appState = appState, - showPostLinkPreview = showPostLinkPreviews, - showIfRead = showIfRead, - voteDisplayMode = voteDisplayMode, - postActionBarMode = postActionBarMode, - swipeToActionPreset = swipeToActionPreset, - disableVideoAutoplay = disableVideoAutoplay, - lowBandwidthMode = lowBandwidthMode, - ).let { - if (!postView.read && markAsReadOnScroll) { - DisposableEffect(key1 = postView.post.id) { - onDispose { - if (listState.isScrollInProgress && index < listState.firstVisibleItemIndex) { - onMarkAsRead(postView) + + if (posts?.isEmpty() == true && !enableInfiniteScroll) { + item(contentType = "no_more_posts") { + Text( + modifier = Modifier.padding(vertical = XL_PADDING).fillMaxWidth(), + textAlign = TextAlign.Center, + text = stringResource(R.string.no_more_posts), + ) + } + } else if (posts != null) { + // List of items + itemsIndexed( + items = posts, + contentType = { _, _ -> "Post" }, + ) { index, postView -> + PostListing( + postView = postView, + admins = admins, + moderators = moderators, + useCustomTabs = useCustomTabs, + usePrivateTabs = usePrivateTabs, + onUpvoteClick = onUpvoteClick, + onDownvoteClick = onDownvoteClick, + onReplyClick = onReplyClick, + onPostClick = onPostClick, + onSaveClick = onSaveClick, + onCommunityClick = onCommunityClick, + onEditPostClick = onEditPostClick, + onDeletePostClick = onDeletePostClick, + onHidePostClick = onHidePostClick, + onReportClick = onReportClick, + onRemoveClick = onRemoveClick, + onBanPersonClick = onBanPersonClick, + onBanFromCommunityClick = onBanFromCommunityClick, + onLockPostClick = onLockPostClick, + onFeaturePostClick = onFeaturePostClick, + onViewVotesClick = onViewPostVotesClick, + onPersonClick = onPersonClick, + showCommunityName = showCommunityName, + fullBody = false, + account = account, + postViewMode = postViewMode, + showVotingArrowsInListView = showVotingArrowsInListView, + enableDownVotes = enableDownVotes, + showAvatar = showAvatar, + blurNSFW = blurNSFW, + appState = appState, + showPostLinkPreview = showPostLinkPreviews, + showIfRead = showIfRead, + voteDisplayMode = voteDisplayMode, + postActionBarMode = postActionBarMode, + swipeToActionPreset = swipeToActionPreset, + disableVideoAutoplay = disableVideoAutoplay, + lowBandwidthMode = lowBandwidthMode, + ).let { + if (!postView.read && markAsReadOnScroll) { + DisposableEffect(key1 = postView.post.id) { + onDispose { + if (listState.isScrollInProgress && index < listState.firstVisibleItemIndex) { + onMarkAsRead(postView) + } } } } } + HorizontalDivider( + modifier = Modifier.padding(vertical = SMALL_PADDING), + color = MaterialTheme.colorScheme.surfaceVariant, + ) } - HorizontalDivider( - modifier = Modifier.padding(vertical = SMALL_PADDING), - color = MaterialTheme.colorScheme.surfaceVariant, - ) } if (enableInfiniteScroll && showPostAppendRetry) { @@ -167,11 +183,12 @@ fun PostListings( } } - if (!enableInfiniteScroll && posts.isNotEmpty()) { + if (!enableInfiniteScroll && posts != null) { item(contentType = "pagination_buttons") { PaginationButton( currentPage = currentPage, - onNext = onNextPage, + onNext = onNextPage, + onNextEnabled = posts.isNotEmpty(), onPrevious = onPreviousPage ) } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 76324bd9e..81c379e62 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -514,4 +514,5 @@ Previous Next No more comments available + No more posts available From 7937f112bf3d27ab6a7e33a9cadddd30ae595e8d Mon Sep 17 00:00:00 2001 From: zangideutschi Date: Mon, 6 Jul 2026 18:12:20 +0000 Subject: [PATCH 11/22] Translated using Weblate (German) Currently translated at 43.1% (220 of 510 strings) Translation: Lemmy/jerboa Translate-URL: http://weblate.join-lemmy.org/projects/lemmy/jerboa/de/ --- app/src/main/res/values-de/strings.xml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 2d8507313..5e0fa487a 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -261,4 +261,11 @@ Nachrichten Gespeichert Markiere Post als NSFW - \ No newline at end of file + Upvotes anzeigen + Downvotes anzeigen + Inhalt entfernen + Kommentar löschen + Beitrag teilen + Beitrag entsperren + Beitrag sperren + From 45fc3f54c12d04f14e24d3d084c7892cb55e59f0 Mon Sep 17 00:00:00 2001 From: zangideutschi Date: Mon, 6 Jul 2026 20:37:11 +0000 Subject: [PATCH 12/22] Translated using Weblate (German) Currently translated at 43.5% (222 of 510 strings) Translation: Lemmy/jerboa Translate-URL: http://weblate.join-lemmy.org/projects/lemmy/jerboa/de/ --- app/src/main/res/values-de/strings.xml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 5e0fa487a..326a4e48a 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -90,7 +90,7 @@ Permalink kopieren Beitrag melden Bearbeiten - Wiederherstellen + wiederherstellen Löschen "zu " "von " @@ -236,7 +236,7 @@ Bewertungspfeile in Listenansicht anzeigen Hochstimmen Runterstimmen - Hochgestimmt + upgevoted Runtergestimmt Konnte Repost nicht erstellen: %1$s Screenshots unterbinden @@ -268,4 +268,6 @@ Beitrag teilen Beitrag entsperren Beitrag sperren + Upvoteverhältnis in % anzeigen + Beitrag entfernen From a0d5033a1a029bc1aeed64b7ff0cbc20298efe7d Mon Sep 17 00:00:00 2001 From: Maarten Vercruysse <67873169+MV-GH@users.noreply.github.com> Date: Wed, 8 Jul 2026 13:47:15 +0200 Subject: [PATCH 13/22] Fix lint due to new unescaped translation from weblate (#2164) --- app/src/main/res/values-de/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 326a4e48a..378e3dc90 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -268,6 +268,6 @@ Beitrag teilen Beitrag entsperren Beitrag sperren - Upvoteverhältnis in % anzeigen + Upvoteverhältnis in %% anzeigen Beitrag entfernen From 2422c79243335009a0ac3599b7e701185b5e30d3 Mon Sep 17 00:00:00 2001 From: Maarten Vercruysse <67873169+MV-GH@users.noreply.github.com> Date: Wed, 8 Jul 2026 14:15:28 +0200 Subject: [PATCH 14/22] Implement view pooling for AndroidViews (#2163) * Implement view pooling for AndroidViews * Fix lint --------- Co-authored-by: Dessalines --- .../ui/components/common/MarkdownHelper.kt | 96 +++++++++++-------- 1 file changed, 58 insertions(+), 38 deletions(-) diff --git a/app/src/main/java/com/jerboa/ui/components/common/MarkdownHelper.kt b/app/src/main/java/com/jerboa/ui/components/common/MarkdownHelper.kt index c9f635c64..13d61606a 100644 --- a/app/src/main/java/com/jerboa/ui/components/common/MarkdownHelper.kt +++ b/app/src/main/java/com/jerboa/ui/components/common/MarkdownHelper.kt @@ -173,16 +173,20 @@ object MarkdownHelper { style: TextStyle = MaterialTheme.typography.bodyLarge, ) { AndroidView( - factory = { ctx -> - createTextView( - context = ctx, + factory = { ctx -> createTextView(context = ctx) }, + update = { textView -> + applyTextStyle( + textView = textView, color = color, style = style, - onClick = onClick, - onLongClick = onLongClick, ) - }, - update = { textView -> + // Set on every recomposition (not just in `factory`), since with onReset + // the underlying View may be pooled/reused for a different item. If we only + // set these in `factory`, a reused View would keep firing the click handlers + // of the item it was originally created for. + textView.setOnClickListener(onClick?.let { click -> View.OnClickListener { click() } }) + textView.setOnLongClickListener(onLongClick) + val parser = markwon!! val md = parser.toMarkdown(markdown) for (img in md.getSpans(0, md.length, AsyncDrawableSpan::class.java)) { @@ -190,19 +194,28 @@ object MarkdownHelper { } parser.setParsedMarkdown(textView, md) }, + onReset = { textView -> + textView.setOnClickListener(null) + textView.setOnLongClickListener(null) + textView.text = null + }, modifier = modifier, ) } - private fun createTextView( - context: Context, - color: Color = Color.Unspecified, + private fun createTextView(context: Context): TextView = + TextView(context).apply { + width = maxWidth + } + + private fun applyTextStyle( + textView: TextView, + color: Color, textAlign: TextAlign? = null, @FontRes fontResource: Int? = null, style: TextStyle, - onClick: (() -> Unit)? = null, - onLongClick: ((View) -> Boolean)? = null, - ): TextView { + ) { + val context = textView.context val textColor = color.takeOrElse { style.color } val mergedStyle = style.merge( @@ -212,15 +225,12 @@ object MarkdownHelper { textAlign = textAlign ?: TextAlign.Unspecified, ), ) - return TextView(context).apply { - onClick?.let { setOnClickListener { onClick() } } - onLongClick?.let { setOnLongClickListener(it) } + textView.apply { setTextColor(textColor.toArgb()) setTextSize(TypedValue.COMPLEX_UNIT_SP, mergedStyle.fontSize.value) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { lineHeight = convertSpToPx(mergedStyle.lineHeight, context) } - width = maxWidth textAlign?.let { align -> textAlignment = @@ -232,9 +242,7 @@ object MarkdownHelper { } } - fontResource?.let { font -> - typeface = ResourcesCompat.getFont(context, font) - } + typeface = fontResource?.let { font -> ResourcesCompat.getFont(context, font) } } } @@ -247,28 +255,48 @@ object MarkdownHelper { style: TextStyle, ) { AndroidView( - factory = { ctx -> - createTextViewPreview( - context = ctx, + factory = { ctx -> createTextViewPreview(context = ctx) }, + update = { textView -> + applyTextStylePreview( + textView = textView, color = color, style = style, - onClick = onClick, ) - }, - update = { textView -> + // Set on every recomposition (not just in `factory`), since with onReset + // the underlying View may be pooled/reused for a different item. If we only + // set this in `factory`, a reused View would keep firing the click handler + // of the item it was originally created for (e.g. opening the wrong post). + textView.setOnClickListener(onClick?.let { click -> View.OnClickListener { click() } }) previewMarkwon?.setMarkdown(textView, markdown) }, + onReset = { textView -> + textView.setOnClickListener(null) + textView.text = null + }, modifier = modifier, ) } private fun createTextViewPreview( context: Context, - color: Color = Color.Unspecified, maxLines: Int = 5, + ): TextView = + TextView(context).apply { + width = maxWidth + layoutParams = LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT) + this.movementMethod = null + this.linksClickable = false + ellipsize = TextUtils.TruncateAt.END + setMaxLines(maxLines) + focusable = NOT_FOCUSABLE + } + + private fun applyTextStylePreview( + textView: TextView, + color: Color, style: TextStyle, - onClick: (() -> Unit)? = null, - ): TextView { + ) { + val context = textView.context val textColor = color.takeOrElse { style.color } val mergedStyle = style.merge( @@ -277,20 +305,12 @@ object MarkdownHelper { fontSize = style.fontSize, ), ) - return TextView(context).apply { - onClick?.let { setOnClickListener { onClick() } } + textView.apply { setTextColor(textColor.toArgb()) setTextSize(TypedValue.COMPLEX_UNIT_SP, mergedStyle.fontSize.value) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { lineHeight = convertSpToPx(mergedStyle.lineHeight, context) } - width = maxWidth - layoutParams = LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT) - this.movementMethod = null - this.linksClickable = false - ellipsize = TextUtils.TruncateAt.END - setMaxLines(maxLines) - focusable = NOT_FOCUSABLE } } } From f7cae6ee12589bc952e62e6e5774d58d73215efa Mon Sep 17 00:00:00 2001 From: Maarten Vercruysse <67873169+MV-GH@users.noreply.github.com> Date: Thu, 9 Jul 2026 01:12:23 +0200 Subject: [PATCH 15/22] Fix scroll stuck/glitchy in comments when scrolling up to an image (#2162) * Fix scroll stuck/glitchy in comments when scrolling up to an image * Remove postId from CommentBody cacheKey --------- Co-authored-by: Dessalines --- .../ui/components/comment/CommentNode.kt | 1 + .../ui/components/common/InputFields.kt | 2 + .../ui/components/common/MarkdownHelper.kt | 38 ++++++++++++++++++- 3 files changed, 40 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/jerboa/ui/components/comment/CommentNode.kt b/app/src/main/java/com/jerboa/ui/components/comment/CommentNode.kt index ea92fb83f..4b502d4ae 100644 --- a/app/src/main/java/com/jerboa/ui/components/comment/CommentNode.kt +++ b/app/src/main/java/com/jerboa/ui/components/comment/CommentNode.kt @@ -159,6 +159,7 @@ fun CommentBody( onClick = onClick, onLongClick = onLongClick, modifier = Modifier.padding(0.dp, 0.dp, 0.dp, MEDIUM_PADDING), + cacheKey = "${comment.id}", ) } } diff --git a/app/src/main/java/com/jerboa/ui/components/common/InputFields.kt b/app/src/main/java/com/jerboa/ui/components/common/InputFields.kt index 56f05d4a2..3aa677bb2 100644 --- a/app/src/main/java/com/jerboa/ui/components/common/InputFields.kt +++ b/app/src/main/java/com/jerboa/ui/components/common/InputFields.kt @@ -631,6 +631,7 @@ fun MyMarkdownText( color: Color = MaterialTheme.colorScheme.onSurface, onClick: () -> Unit, onLongClick: ((View) -> Boolean)? = null, + cacheKey: String? = null, ) { MarkdownHelper.CreateMarkdownView( markdown = markdown, @@ -638,6 +639,7 @@ fun MyMarkdownText( onClick = onClick, onLongClick = onLongClick, modifier = modifier, + cacheKey = cacheKey, ) } diff --git a/app/src/main/java/com/jerboa/ui/components/common/MarkdownHelper.kt b/app/src/main/java/com/jerboa/ui/components/common/MarkdownHelper.kt index 13d61606a..694914535 100644 --- a/app/src/main/java/com/jerboa/ui/components/common/MarkdownHelper.kt +++ b/app/src/main/java/com/jerboa/ui/components/common/MarkdownHelper.kt @@ -2,8 +2,10 @@ package com.jerboa.ui.components.common import android.content.Context import android.os.Build +import android.text.Spanned import android.text.TextUtils import android.text.util.Linkify +import android.util.LruCache import android.util.TypedValue import android.view.View import android.view.View.NOT_FOCUSABLE @@ -77,6 +79,23 @@ object MarkdownHelper { private var markwon: Markwon? = null private var previewMarkwon: Markwon? = null + /** + * Caches the parsed [Spanned] result of a markdown string, keyed by [CachedMarkdown]. + * + * This exists because items inside a LazyColumn are fully disposed and recreated (including + * their underlying AndroidView) whenever they scroll out of and back into the viewport. Without + * this cache, every re-entry into the viewport would re-parse the markdown from scratch and + * recreate the image spans, causing embedded images to visibly pop out and back in on every + * scroll pass. Reusing the same Spanned (and thus the same already-resolved image drawables) + * across recreations avoids that. + */ + private val parsedMarkdownCache = LruCache(500) + + private data class CachedMarkdown( + val sourceText: String, + val spanned: Spanned, + ) + fun init( appState: JerboaAppState, useCustomTabs: Boolean, @@ -171,6 +190,15 @@ object MarkdownHelper { onClick: (() -> Unit)? = null, onLongClick: ((View) -> Boolean)? = null, style: TextStyle = MaterialTheme.typography.bodyLarge, + // Stable identifier (e.g. "${postId}_${commentId}") used to cache the parsed markdown + // across recompositions/view recreations. Prefer a unique per-item id over falling back + // to the markdown text: two different items can have identical text, and a shared Spanned + // means sharing its AsyncDrawableSpan/AsyncDrawable image spans too. Android's + // Drawable.setCallback() only tracks one callback at a time, so if two such items are on + // screen simultaneously, only the most recently scheduled one reliably repaints when its + // image loads - the other can show a stale/blank image. Falls back to the markdown text + // itself when no id is available, which is still correct as long as that scenario can't occur. + cacheKey: String? = null, ) { AndroidView( factory = { ctx -> createTextView(context = ctx) }, @@ -188,7 +216,15 @@ object MarkdownHelper { textView.setOnLongClickListener(onLongClick) val parser = markwon!! - val md = parser.toMarkdown(markdown) + val effectiveKey = cacheKey ?: markdown + val cached = parsedMarkdownCache.get(effectiveKey) + val md = if (cached != null && cached.sourceText == markdown) { + cached.spanned + } else { + parser.toMarkdown(markdown).also { + parsedMarkdownCache.put(effectiveKey, CachedMarkdown(markdown, it)) + } + } for (img in md.getSpans(0, md.length, AsyncDrawableSpan::class.java)) { img.drawable.initWithKnownDimensions(textView.width, textView.textSize) } From 0563a9007f654b48e86f66c0e3bd123fdad222d4 Mon Sep 17 00:00:00 2001 From: rodrigo-fm Date: Thu, 9 Jul 2026 23:35:21 -0300 Subject: [PATCH 16/22] [pagination-scroll] - fixes floating action button not disappearing when first starting the app. --- .../java/com/jerboa/ui/components/community/CommunityScreen.kt | 2 +- app/src/main/java/com/jerboa/ui/components/home/HomeActivity.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/jerboa/ui/components/community/CommunityScreen.kt b/app/src/main/java/com/jerboa/ui/components/community/CommunityScreen.kt index 33037a781..0bf0383bc 100644 --- a/app/src/main/java/com/jerboa/ui/components/community/CommunityScreen.kt +++ b/app/src/main/java/com/jerboa/ui/components/community/CommunityScreen.kt @@ -109,7 +109,7 @@ fun CommunityScreen( viewModel(factory = CommunityViewModel.Companion.Factory(communityArg)) val postListState = communityViewModel.lazyListState - val hideFab by remember { + val hideFab by remember(enableInfiniteScroll) { derivedStateOf { postListState.isScrolledToEnd() && !enableInfiniteScroll } } diff --git a/app/src/main/java/com/jerboa/ui/components/home/HomeActivity.kt b/app/src/main/java/com/jerboa/ui/components/home/HomeActivity.kt index cbe2574e4..bf3ffb912 100644 --- a/app/src/main/java/com/jerboa/ui/components/home/HomeActivity.kt +++ b/app/src/main/java/com/jerboa/ui/components/home/HomeActivity.kt @@ -120,7 +120,7 @@ fun HomeScreen( // Forget snackbars of previous accounts val snackbarHostState = remember(account) { SnackbarHostState() } - val hideFab by remember { + val hideFab by remember(enableInfiniteScroll) { derivedStateOf { postListState.isScrolledToEnd() && !enableInfiniteScroll } } From 8723976ef3c3162053bb4a4774d76943ee0b1c8c Mon Sep 17 00:00:00 2001 From: rodrigo-fm Date: Thu, 9 Jul 2026 23:45:51 -0300 Subject: [PATCH 17/22] [pagination-scroll] - fixes community previous page showing main page content instead of the selected community. --- app/src/main/java/com/jerboa/model/CommunityViewModel.kt | 6 ++++++ app/src/main/java/com/jerboa/model/PostsViewModel.kt | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/jerboa/model/CommunityViewModel.kt b/app/src/main/java/com/jerboa/model/CommunityViewModel.kt index 8d6b5acc4..9b4caa14d 100644 --- a/app/src/main/java/com/jerboa/model/CommunityViewModel.kt +++ b/app/src/main/java/com/jerboa/model/CommunityViewModel.kt @@ -119,6 +119,12 @@ class CommunityViewModel( init() } + override fun getPreviousPageForm(): GetPosts = + super.getPreviousPageForm().copy( + community_id = communityId, + community_name = communityName, + ) + override fun getForm(): GetPosts = super.getForm().copy( community_id = communityId, diff --git a/app/src/main/java/com/jerboa/model/PostsViewModel.kt b/app/src/main/java/com/jerboa/model/PostsViewModel.kt index db9b23b7c..78ee26caa 100644 --- a/app/src/main/java/com/jerboa/model/PostsViewModel.kt +++ b/app/src/main/java/com/jerboa/model/PostsViewModel.kt @@ -151,7 +151,7 @@ open class PostsViewModel( fun refreshPosts() = resetPosts(ApiState.Refreshing) - private fun getPreviousPageForm() = GetPosts( + protected open fun getPreviousPageForm() = GetPosts( page = pageController.page, page_cursor = pageController.currentPageCursor, sort = sortType, From cb41470f45284cdffeb0d842d8696600e799ede6 Mon Sep 17 00:00:00 2001 From: rodrigo-fm Date: Fri, 10 Jul 2026 00:06:42 -0300 Subject: [PATCH 18/22] [pagination-scroll] - fixes next and previous page buttons not scrolling to the top of the screen after loading posts. --- .../main/java/com/jerboa/ui/components/post/PostListings.kt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/src/main/java/com/jerboa/ui/components/post/PostListings.kt b/app/src/main/java/com/jerboa/ui/components/post/PostListings.kt index 35603fead..45293f33e 100644 --- a/app/src/main/java/com/jerboa/ui/components/post/PostListings.kt +++ b/app/src/main/java/com/jerboa/ui/components/post/PostListings.kt @@ -13,6 +13,7 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Modifier import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.stringResource @@ -96,6 +97,11 @@ fun PostListings( disableVideoAutoplay: Boolean, lowBandwidthMode: Boolean, ) { + + LaunchedEffect(posts) { + if (!enableInfiniteScroll) listState.scrollToItem(index = 0) + } + LazyColumn( state = listState, modifier = Modifier From 00a691ef17966817bed5b2b9483a3ad846b7afa1 Mon Sep 17 00:00:00 2001 From: rodrigo-fm Date: Fri, 10 Jul 2026 22:14:37 -0300 Subject: [PATCH 19/22] [pagination-scroll] - refreshes the home screen after toggling between enable/disable infinite scrolling. --- .../com/jerboa/ui/components/home/HomeActivity.kt | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/app/src/main/java/com/jerboa/ui/components/home/HomeActivity.kt b/app/src/main/java/com/jerboa/ui/components/home/HomeActivity.kt index bf3ffb912..5edbef254 100644 --- a/app/src/main/java/com/jerboa/ui/components/home/HomeActivity.kt +++ b/app/src/main/java/com/jerboa/ui/components/home/HomeActivity.kt @@ -27,8 +27,11 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.input.nestedscroll.nestedScroll @@ -123,6 +126,7 @@ fun HomeScreen( val hideFab by remember(enableInfiniteScroll) { derivedStateOf { postListState.isScrolledToEnd() && !enableInfiniteScroll } } + var currentEnableInfiniteScroll by rememberSaveable { mutableStateOf(enableInfiniteScroll) } appState.ConsumeReturn(PostEditReturn.POST_VIEW, homeViewModel::updatePost) appState.ConsumeReturn(PostRemoveReturn.POST_VIEW, homeViewModel::updatePost) @@ -130,6 +134,14 @@ fun HomeScreen( appState.ConsumeReturn(BanPersonReturn.PERSON_VIEW, homeViewModel::updateBanned) appState.ConsumeReturn(BanFromCommunityReturn.BAN_DATA_VIEW, homeViewModel::updateBannedFromCommunity) + LaunchedEffect(enableInfiniteScroll) { + if (currentEnableInfiniteScroll != enableInfiniteScroll) { + currentEnableInfiniteScroll = enableInfiniteScroll + postListState.scrollToItem(0) + homeViewModel.resetPosts() + } + } + LaunchedEffect(account) { if (!account.isAnon() && !account.isReady()) { account.doIfReadyElseDisplayInfo( From 369ae5c5f5eb8793ce0d30d944ced860e0f25d14 Mon Sep 17 00:00:00 2001 From: rodrigo-fm Date: Sun, 19 Jul 2026 21:11:48 -0300 Subject: [PATCH 20/22] [toggle-pagination] - moves infinite scroll option to a different table and also saves it in the API. --- app/src/main/java/com/jerboa/MainActivity.kt | 8 ++++---- app/src/main/java/com/jerboa/db/AppDB.kt | 2 -- app/src/main/java/com/jerboa/db/AppDBMigrations.kt | 2 +- app/src/main/java/com/jerboa/db/entity/Account.kt | 6 ++++++ .../main/java/com/jerboa/db/entity/AppSettings.kt | 6 ------ .../com/jerboa/model/AccountSettingsViewModel.kt | 1 + .../main/java/com/jerboa/model/LoginViewModel.kt | 1 + app/src/main/java/com/jerboa/model/SiteViewModel.kt | 1 + .../jerboa/ui/components/home/BottomNavScreen.kt | 6 +++--- .../components/settings/account/AccountSettings.kt | 8 ++++++++ .../settings/lookandfeel/LookAndFeelScreen.kt | 13 ------------- 11 files changed, 25 insertions(+), 29 deletions(-) diff --git a/app/src/main/java/com/jerboa/MainActivity.kt b/app/src/main/java/com/jerboa/MainActivity.kt index 09e1b2dba..a63d0a34f 100644 --- a/app/src/main/java/com/jerboa/MainActivity.kt +++ b/app/src/main/java/com/jerboa/MainActivity.kt @@ -272,7 +272,7 @@ class MainActivity : AppCompatActivity() { swipeToActionPreset = appSettings.swipeToActionPreset.toEnum(), disableVideoAutoplay = appSettings.disableVideoAutoplay.toBool(), lowBandwidthMode = lowBandwidthMode, - enableInfiniteScroll = appSettings.enableInfiniteScroll + enableInfiniteScroll = accountViewModel.currentAccount.value?.infiniteScrollEnabled ?: true ) } @@ -319,7 +319,7 @@ class MainActivity : AppCompatActivity() { swipeToActionPreset = appSettings.swipeToActionPreset.toEnum(), disableVideoAutoplay = appSettings.disableVideoAutoplay.toBool(), lowBandwidthMode = lowBandwidthMode, - enableInfiniteScroll = appSettings.enableInfiniteScroll + enableInfiniteScroll = accountViewModel.currentAccount.value?.infiniteScrollEnabled ?: true ) } @@ -364,7 +364,7 @@ class MainActivity : AppCompatActivity() { swipeToActionPreset = appSettings.swipeToActionPreset.toEnum(), disableVideoAutoplay = appSettings.disableVideoAutoplay.toBool(), lowBandwidthMode = lowBandwidthMode, - enableInfiniteScroll = appSettings.enableInfiniteScroll + enableInfiniteScroll = accountViewModel.currentAccount.value?.infiniteScrollEnabled ?: true ) } @@ -406,7 +406,7 @@ class MainActivity : AppCompatActivity() { swipeToActionPreset = appSettings.swipeToActionPreset.toEnum(), disableVideoAutoplay = appSettings.disableVideoAutoplay.toBool(), lowBandwidthMode = lowBandwidthMode, - enableInfiniteScroll = appSettings.enableInfiniteScroll + enableInfiniteScroll = accountViewModel.currentAccount.value?.infiniteScrollEnabled ?: true ) } diff --git a/app/src/main/java/com/jerboa/db/AppDB.kt b/app/src/main/java/com/jerboa/db/AppDB.kt index 2f83a75ad..c194ab29f 100644 --- a/app/src/main/java/com/jerboa/db/AppDB.kt +++ b/app/src/main/java/com/jerboa/db/AppDB.kt @@ -27,7 +27,6 @@ const val DEFAULT_LAST_VERSION_CODE_VIEWED = 0 const val DEFAULT_POST_VIEW_MODE = 0 const val DEFAULT_POST_NAVIGATION_GESTURE_MODE = 0 const val DEFAULT_SHOW_BOTTOM_NAV = 1 -const val DEFAULT_ENABLE_IFINITE_SCROLL = 1 const val DEFAULT_SHOW_COLLAPSED_COMMENT_CONTENT = 0 const val DEFAULT_SHOW_COMMENT_ACTION_BAR_BY_DEFAULT = 1 const val DEFAULT_SHOW_VOTING_ARROWS_IN_LIST_VIEW = 1 @@ -75,7 +74,6 @@ val APP_SETTINGS_DEFAULT = swipeToActionPreset = DEFAULT_SWIPE_TO_ACTION_PRESET, disableVideoAutoplay = DEFAULT_DISABLE_AUTO_PLAY, lowBandwidthMode = DEFAULT_LOW_BANDWIDTH_MODE, - enableInfiniteScroll = DEFAULT_ENABLE_IFINITE_SCROLL.toBool() ) @Database( diff --git a/app/src/main/java/com/jerboa/db/AppDBMigrations.kt b/app/src/main/java/com/jerboa/db/AppDBMigrations.kt index 77c0ca47d..d5bf33fa8 100644 --- a/app/src/main/java/com/jerboa/db/AppDBMigrations.kt +++ b/app/src/main/java/com/jerboa/db/AppDBMigrations.kt @@ -579,7 +579,7 @@ val MIGRATION_35_36 = object : Migration(35, 36) { override fun migrate(db: SupportSQLiteDatabase) { db.execSQL( - "ALTER TABLE AppSettings ADD COLUMN enable_infinite_scroll INTEGER NOT NULL DEFAULT 1", + "ALTER TABLE Account ADD COLUMN infinite_scroll_enabled INTEGER NOT NULL DEFAULT 1", ) } } diff --git a/app/src/main/java/com/jerboa/db/entity/Account.kt b/app/src/main/java/com/jerboa/db/entity/Account.kt index 83d84b4e8..4f47a6afc 100644 --- a/app/src/main/java/com/jerboa/db/entity/Account.kt +++ b/app/src/main/java/com/jerboa/db/entity/Account.kt @@ -23,6 +23,11 @@ data class Account( defaultValue = "0", ) val defaultSortType: Int, + @ColumnInfo( + name = "infinite_scroll_enabled", + defaultValue = "1" + ) + val infiniteScrollEnabled: Boolean, @ColumnInfo( name = "verification_state", defaultValue = "0", @@ -43,6 +48,7 @@ val AnonAccount = defaultListingType = 1, defaultSortType = 0, verificationState = 0, + infiniteScrollEnabled = true, isAdmin = false, isMod = false, ) diff --git a/app/src/main/java/com/jerboa/db/entity/AppSettings.kt b/app/src/main/java/com/jerboa/db/entity/AppSettings.kt index dd8545820..e38f8e8a4 100644 --- a/app/src/main/java/com/jerboa/db/entity/AppSettings.kt +++ b/app/src/main/java/com/jerboa/db/entity/AppSettings.kt @@ -7,7 +7,6 @@ import com.jerboa.db.DEFAULT_AUTO_PLAY_GIFS import com.jerboa.db.DEFAULT_BACK_CONFIRMATION_MODE import com.jerboa.db.DEFAULT_BLUR_NSFW import com.jerboa.db.DEFAULT_DISABLE_AUTO_PLAY -import com.jerboa.db.DEFAULT_ENABLE_IFINITE_SCROLL import com.jerboa.db.DEFAULT_LAST_VERSION_CODE_VIEWED import com.jerboa.db.DEFAULT_LOW_BANDWIDTH_MODE import com.jerboa.db.DEFAULT_MARK_AS_READ_ON_SCROLL @@ -59,11 +58,6 @@ data class AppSettings( defaultValue = DEFAULT_SHOW_BOTTOM_NAV.toString(), ) val showBottomNav: Boolean, - @ColumnInfo( - name = "enable_infinite_scroll", - defaultValue = DEFAULT_ENABLE_IFINITE_SCROLL.toString(), - ) - val enableInfiniteScroll: Boolean, @ColumnInfo( name = "post_navigation_gesture_mode", defaultValue = DEFAULT_POST_NAVIGATION_GESTURE_MODE.toString(), diff --git a/app/src/main/java/com/jerboa/model/AccountSettingsViewModel.kt b/app/src/main/java/com/jerboa/model/AccountSettingsViewModel.kt index 47ee81b3e..9f080c034 100644 --- a/app/src/main/java/com/jerboa/model/AccountSettingsViewModel.kt +++ b/app/src/main/java/com/jerboa/model/AccountSettingsViewModel.kt @@ -60,6 +60,7 @@ class AccountSettingsViewModel( ): Account { val newAccount = account.copy( + infiniteScrollEnabled = form.infinite_scroll_enabled ?: account.infiniteScrollEnabled, defaultListingType = form.default_listing_type?.ordinal ?: account.defaultListingType, defaultSortType = form.default_sort_type?.ordinal ?: account.defaultSortType, ) diff --git a/app/src/main/java/com/jerboa/model/LoginViewModel.kt b/app/src/main/java/com/jerboa/model/LoginViewModel.kt index f609a8003..809e2c995 100644 --- a/app/src/main/java/com/jerboa/model/LoginViewModel.kt +++ b/app/src/main/java/com/jerboa/model/LoginViewModel.kt @@ -111,6 +111,7 @@ class LoginViewModel : ViewModel() { defaultSortType = luv.local_user.default_sort_type.ordinal, verificationState = 0, isAdmin = luv.local_user.admin, + infiniteScrollEnabled = luv.local_user.infinite_scroll_enabled, isMod = mui.moderates.isNotEmpty(), ) diff --git a/app/src/main/java/com/jerboa/model/SiteViewModel.kt b/app/src/main/java/com/jerboa/model/SiteViewModel.kt index 414e7bc49..a870d0feb 100644 --- a/app/src/main/java/com/jerboa/model/SiteViewModel.kt +++ b/app/src/main/java/com/jerboa/model/SiteViewModel.kt @@ -107,6 +107,7 @@ class SiteViewModel( if (currAcc != null) { val newAccount = currAcc.copy( + infiniteScrollEnabled = localUser.infinite_scroll_enabled, defaultListingType = localUser.default_listing_type.ordinal, defaultSortType = localUser.default_sort_type.ordinal, isAdmin = localUser.admin, diff --git a/app/src/main/java/com/jerboa/ui/components/home/BottomNavScreen.kt b/app/src/main/java/com/jerboa/ui/components/home/BottomNavScreen.kt index 42dd5e12a..206191d1f 100644 --- a/app/src/main/java/com/jerboa/ui/components/home/BottomNavScreen.kt +++ b/app/src/main/java/com/jerboa/ui/components/home/BottomNavScreen.kt @@ -268,7 +268,7 @@ fun BottomNavScreen( disableVideoAutoplay = appSettings.disableVideoAutoplay.toBool(), lowBandwidthMode = lowBandwidthMode, padding = padding, - enableInfiniteScroll = appSettings.enableInfiniteScroll + enableInfiniteScroll = accountViewModel.currentAccount.value?.infiniteScrollEnabled ?: true ) } @@ -339,7 +339,7 @@ fun BottomNavScreen( swipeToActionPreset = appSettings.swipeToActionPreset.toEnum(), disableVideoAutoplay = appSettings.disableVideoAutoplay.toBool(), lowBandwidthMode = lowBandwidthMode, - enableInfiniteScroll = appSettings.enableInfiniteScroll, + enableInfiniteScroll = accountViewModel.currentAccount.value?.infiniteScrollEnabled ?: true, padding = padding ) } @@ -364,7 +364,7 @@ fun BottomNavScreen( swipeToActionPreset = appSettings.swipeToActionPreset.toEnum(), disableVideoAutoplay = appSettings.disableVideoAutoplay.toBool(), lowBandwidthMode = lowBandwidthMode, - enableInfiniteScroll = appSettings.enableInfiniteScroll, + enableInfiniteScroll = accountViewModel.currentAccount.value?.infiniteScrollEnabled ?: true, padding = padding, ) } diff --git a/app/src/main/java/com/jerboa/ui/components/settings/account/AccountSettings.kt b/app/src/main/java/com/jerboa/ui/components/settings/account/AccountSettings.kt index 2c801f567..e00a575f1 100644 --- a/app/src/main/java/com/jerboa/ui/components/settings/account/AccountSettings.kt +++ b/app/src/main/java/com/jerboa/ui/components/settings/account/AccountSettings.kt @@ -145,6 +145,7 @@ fun SettingsForm( val supportedListingTypes = remember { getSupportedEntries(API.version) } val defaultListingTypeState = remember { mutableStateOf(ListingType.entries[luv?.local_user?.default_listing_type?.ordinal ?: 0]) } + val infiniteScrollEnabled = remember { mutableStateOf(luv?.local_user?.infinite_scroll_enabled ?: true) } val showNsfwState = remember { mutableStateOf(luv?.local_user?.show_nsfw ?: false) } val showAvatarsState = remember { mutableStateOf(luv?.local_user?.show_avatars ?: false) } val showScoresStateLegacy = remember { mutableStateOf(luv?.local_user?.show_scores ?: true) } @@ -166,6 +167,7 @@ fun SettingsForm( banner = banner, matrix_user_id = matrixUserId, interface_language = interfaceLang, + infinite_scroll_enabled = infiniteScrollEnabled.value, bot_account = botAccountState.value, default_sort_type = supportedSortTypes[defaultSortTypeState.value.ordinal], send_notifications_to_email = sendNotificationsToEmailState.value, @@ -288,6 +290,12 @@ fun SettingsForm( }, ) + SwitchPreference( + state = infiniteScrollEnabled, + title = { + Text(text = stringResource(R.string.look_and_feel_enable_infinite_scroll)) + }, + ) SwitchPreference( state = showNsfwState, title = { diff --git a/app/src/main/java/com/jerboa/ui/components/settings/lookandfeel/LookAndFeelScreen.kt b/app/src/main/java/com/jerboa/ui/components/settings/lookandfeel/LookAndFeelScreen.kt index f0fdaec21..a9879a3b8 100644 --- a/app/src/main/java/com/jerboa/ui/components/settings/lookandfeel/LookAndFeelScreen.kt +++ b/app/src/main/java/com/jerboa/ui/components/settings/lookandfeel/LookAndFeelScreen.kt @@ -88,7 +88,6 @@ fun LookAndFeelScreen( var swipeToActionPresetState by remember { mutableStateOf(SwipeToActionPreset.entries[settings.swipeToActionPreset]) } var showBottomNavState by remember { mutableStateOf(settings.showBottomNav) } - var enableInfiniteScrollState by remember { mutableStateOf(settings.enableInfiniteScroll) } var showTextDescriptionsInNavbarState by remember { mutableStateOf(settings.showTextDescriptionsInNavbar) } var showCollapsedCommentContentState by remember { mutableStateOf(settings.showCollapsedCommentContent) } var showCommentActionBarByDefaultState by remember { mutableStateOf(settings.showCommentActionBarByDefault) } @@ -118,7 +117,6 @@ fun LookAndFeelScreen( fontSize = fontSizeState.toInt(), postViewMode = postViewModeState.ordinal, showBottomNav = showBottomNavState, - enableInfiniteScroll = enableInfiniteScrollState, showCollapsedCommentContent = showCollapsedCommentContentState, showCommentActionBarByDefault = showCommentActionBarByDefaultState, showVotingArrowsInListView = showVotingArrowsInListViewState, @@ -431,17 +429,6 @@ fun LookAndFeelScreen( }, ) - SwitchPreference( - value = enableInfiniteScrollState, - onValueChange = { - enableInfiniteScrollState = it - updateAppSettings() - }, - title = { - Text(text = stringResource(R.string.look_and_feel_enable_infinite_scroll)) - }, - ) - SwitchPreference( enabled = showBottomNavState, value = showTextDescriptionsInNavbarState, From 52ed542318dd3fc6ba6eee31d135dd4e96d2b0eb Mon Sep 17 00:00:00 2001 From: Rodrigo Date: Thu, 23 Jul 2026 18:22:17 -0300 Subject: [PATCH 21/22] [toggle-pagination] - renames PaginationButton and moves it to another file. --- .../ui/components/comment/CommentNodes.kt | 6 +-- .../jerboa/ui/components/common/Buttons.kt | 37 -------------- .../jerboa/ui/components/common/Paginator.kt | 49 +++++++++++++++++++ .../jerboa/ui/components/post/PostListings.kt | 4 +- 4 files changed, 54 insertions(+), 42 deletions(-) create mode 100644 app/src/main/java/com/jerboa/ui/components/common/Paginator.kt diff --git a/app/src/main/java/com/jerboa/ui/components/comment/CommentNodes.kt b/app/src/main/java/com/jerboa/ui/components/comment/CommentNodes.kt index d008c7677..a74448018 100644 --- a/app/src/main/java/com/jerboa/ui/components/comment/CommentNodes.kt +++ b/app/src/main/java/com/jerboa/ui/components/comment/CommentNodes.kt @@ -23,7 +23,7 @@ import com.jerboa.datatypes.BanFromCommunityData import com.jerboa.db.entity.Account import com.jerboa.feat.BlurNSFW import com.jerboa.feat.SwipeToActionPreset -import com.jerboa.ui.components.common.PaginationButton +import com.jerboa.ui.components.common.Paginator import it.vercruysse.lemmyapi.datatypes.CommentId import it.vercruysse.lemmyapi.datatypes.CommentView import it.vercruysse.lemmyapi.datatypes.Community @@ -134,7 +134,7 @@ fun CommentNodes( } item { if (!enableInfiniteScroll) { - PaginationButton( + Paginator( currentPage = page, onNext = nextPage, onPrevious = previousPage, @@ -160,7 +160,7 @@ private fun NoMoreComments( textAlign = TextAlign.Center, text = stringResource(R.string.no_more_comments) ) - PaginationButton( + Paginator( currentPage = page, onNext = { }, onPrevious = previousPage, diff --git a/app/src/main/java/com/jerboa/ui/components/common/Buttons.kt b/app/src/main/java/com/jerboa/ui/components/common/Buttons.kt index bf4a980ef..d2b52bdcc 100644 --- a/app/src/main/java/com/jerboa/ui/components/common/Buttons.kt +++ b/app/src/main/java/com/jerboa/ui/components/common/Buttons.kt @@ -1,7 +1,5 @@ package com.jerboa.ui.components.common -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.material3.Button @@ -9,7 +7,6 @@ import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview @@ -34,40 +31,6 @@ fun RetryLoadingPosts(onClick: () -> Unit) { } } -@Composable -fun PaginationButton( - currentPage: Long, - onNext: () -> Unit, - onPrevious: () -> Unit, - onNextEnabled: Boolean = true -) { - Row( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = XXL_PADDING), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically - ) { - Button(onClick = onPrevious, enabled = currentPage > 1) { - Text(stringResource(R.string.previous)) - } - Text(stringResource(R.string.page_number, currentPage)) - Button(onClick = onNext, enabled = onNextEnabled) { - Text(stringResource(R.string.next)) - } - } -} - -@Composable -@Preview(showBackground = true) -private fun PaginationButtonPreview() { - PaginationButton( - currentPage = 1, - onNext = { }, - onPrevious = { } - ) -} - @Composable @Preview(showBackground = true) private fun RetryLoadingPostsPreview() { diff --git a/app/src/main/java/com/jerboa/ui/components/common/Paginator.kt b/app/src/main/java/com/jerboa/ui/components/common/Paginator.kt new file mode 100644 index 000000000..89a6a21bd --- /dev/null +++ b/app/src/main/java/com/jerboa/ui/components/common/Paginator.kt @@ -0,0 +1,49 @@ +package com.jerboa.ui.components.common + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Button +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import com.jerboa.R +import com.jerboa.ui.theme.XXL_PADDING + +@Composable +fun Paginator( + currentPage: Long, + onNext: () -> Unit, + onPrevious: () -> Unit, + onNextEnabled: Boolean = true +) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = XXL_PADDING), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Button(onClick = onPrevious, enabled = currentPage > 1) { + Text(stringResource(R.string.previous)) + } + Text(stringResource(R.string.page_number, currentPage)) + Button(onClick = onNext, enabled = onNextEnabled) { + Text(stringResource(R.string.next)) + } + } +} + +@Composable +@Preview(showBackground = true) +private fun PaginatorPreview() { + Paginator( + currentPage = 1, + onNext = { }, + onPrevious = { } + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/jerboa/ui/components/post/PostListings.kt b/app/src/main/java/com/jerboa/ui/components/post/PostListings.kt index 45293f33e..410134afc 100644 --- a/app/src/main/java/com/jerboa/ui/components/post/PostListings.kt +++ b/app/src/main/java/com/jerboa/ui/components/post/PostListings.kt @@ -33,7 +33,7 @@ import com.jerboa.feat.PostActionBarMode import com.jerboa.feat.SwipeToActionPreset import com.jerboa.feat.default import com.jerboa.rememberJerboaAppState -import com.jerboa.ui.components.common.PaginationButton +import com.jerboa.ui.components.common.Paginator import com.jerboa.ui.components.common.RetryLoadingPosts import com.jerboa.ui.components.common.TriggerWhenReachingEnd import com.jerboa.ui.theme.SMALL_PADDING @@ -191,7 +191,7 @@ fun PostListings( if (!enableInfiniteScroll && posts != null) { item(contentType = "pagination_buttons") { - PaginationButton( + Paginator( currentPage = currentPage, onNext = onNextPage, onNextEnabled = posts.isNotEmpty(), From 916415ef2906ec73b4b6396426d5e11d70a23e47 Mon Sep 17 00:00:00 2001 From: Rodrigo Date: Thu, 23 Jul 2026 21:53:13 -0300 Subject: [PATCH 22/22] [toggle-pagination] - moves 100.dp size to Sizes.kt --- .../java/com/jerboa/ui/components/comment/CommentNodes.kt | 4 ++-- app/src/main/java/com/jerboa/ui/theme/Sizes.kt | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/jerboa/ui/components/comment/CommentNodes.kt b/app/src/main/java/com/jerboa/ui/components/comment/CommentNodes.kt index a74448018..bcb263f72 100644 --- a/app/src/main/java/com/jerboa/ui/components/comment/CommentNodes.kt +++ b/app/src/main/java/com/jerboa/ui/components/comment/CommentNodes.kt @@ -14,7 +14,6 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.unit.dp import com.jerboa.CommentNode import com.jerboa.CommentNodeData import com.jerboa.MissingCommentNode @@ -24,6 +23,7 @@ import com.jerboa.db.entity.Account import com.jerboa.feat.BlurNSFW import com.jerboa.feat.SwipeToActionPreset import com.jerboa.ui.components.common.Paginator +import com.jerboa.ui.theme.SCREEN_BOTTOM_PADDING import it.vercruysse.lemmyapi.datatypes.CommentId import it.vercruysse.lemmyapi.datatypes.CommentView import it.vercruysse.lemmyapi.datatypes.Community @@ -130,7 +130,7 @@ fun CommentNodes( swipeToActionPreset = swipeToActionPreset, ) item { - if (enableInfiniteScroll) Spacer(modifier = Modifier.height(100.dp)) + if (enableInfiniteScroll) Spacer(modifier = Modifier.height(SCREEN_BOTTOM_PADDING)) } item { if (!enableInfiniteScroll) { diff --git a/app/src/main/java/com/jerboa/ui/theme/Sizes.kt b/app/src/main/java/com/jerboa/ui/theme/Sizes.kt index d34af0a6e..60d5e8dc5 100644 --- a/app/src/main/java/com/jerboa/ui/theme/Sizes.kt +++ b/app/src/main/java/com/jerboa/ui/theme/Sizes.kt @@ -22,6 +22,7 @@ val PROFILE_BANNER_SIZE = 128.dp val LINK_ICON_SIZE = 36.dp val POST_LINK_PIC_SIZE = 70.dp val THUMBNAIL_CARET_SIZE = 10.dp +val SCREEN_BOTTOM_PADDING = 100.dp val DRAWER_ITEM_SPACING = 24.dp