Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
a7dbb94
[pagination-scroll] - creates option to enable/disable infinite scroll.
Jul 2, 2026
3fe222a
[pagination-scroll] - creates pagination buttons.
Jul 4, 2026
18ee28e
[pagination-scroll] - adds functionality to the next page button.
rodrigo-fm Jul 4, 2026
adca5f5
[pagination-scroll] - adds functionality to the previous page button.
rodrigo-fm Jul 6, 2026
974a481
[pagination-scroll] - hides floating action button when reaching the …
rodrigo-fm Jul 8, 2026
f0d1f0f
[pagination-scroll] - adds pagination to a user's posts.
rodrigo-fm Jul 8, 2026
80b0f5d
[pagination-scroll] - adds pagination to a user's comments.
rodrigo-fm Jul 9, 2026
95e3c35
Changing renovate to monthly (#2161)
dessalines Jul 3, 2026
c75a1d5
Translated using Weblate (Bulgarian)
salif Jul 5, 2026
588638d
[pagination-scroll] - adds a "no more posts available" string to pagi…
rodrigo-fm Jul 10, 2026
7937f11
Translated using Weblate (German)
Zangideutschi Jul 6, 2026
45fc3f5
Translated using Weblate (German)
Zangideutschi Jul 6, 2026
a0d5033
Fix lint due to new unescaped translation from weblate (#2164)
MV-GH Jul 8, 2026
2422c79
Implement view pooling for AndroidViews (#2163)
MV-GH Jul 8, 2026
f7cae6e
Fix scroll stuck/glitchy in comments when scrolling up to an image (#…
MV-GH Jul 8, 2026
0563a90
[pagination-scroll] - fixes floating action button not disappearing w…
rodrigo-fm Jul 10, 2026
8723976
[pagination-scroll] - fixes community previous page showing main page…
rodrigo-fm Jul 10, 2026
cb41470
[pagination-scroll] - fixes next and previous page buttons not scroll…
rodrigo-fm Jul 10, 2026
00a691e
[pagination-scroll] - refreshes the home screen after toggling betwee…
rodrigo-fm Jul 11, 2026
22752eb
Merge branch 'main' into feature/toggle-pagination
rodrigo-fm Jul 13, 2026
369ae5c
[toggle-pagination] - moves infinite scroll option to a different tab…
rodrigo-fm Jul 20, 2026
f4f97ec
Merge branch 'main' into feature/toggle-pagination
rodrigo-fm Jul 20, 2026
a6c2aa2
Merge branch 'feature/toggle-pagination' of https://github.com/rodrig…
rodrigo-fm Jul 20, 2026
52ed542
[toggle-pagination] - renames PaginationButton and moves it to anothe…
rodrigo-fm Jul 23, 2026
916415e
[toggle-pagination] - moves 100.dp size to Sizes.kt
rodrigo-fm Jul 24, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions app/src/main/java/com/jerboa/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,7 @@ class MainActivity : AppCompatActivity() {
swipeToActionPreset = appSettings.swipeToActionPreset.toEnum(),
disableVideoAutoplay = appSettings.disableVideoAutoplay.toBool(),
lowBandwidthMode = lowBandwidthMode,
enableInfiniteScroll = accountViewModel.currentAccount.value?.infiniteScrollEnabled ?: true
)
}

Expand Down Expand Up @@ -318,6 +319,7 @@ class MainActivity : AppCompatActivity() {
swipeToActionPreset = appSettings.swipeToActionPreset.toEnum(),
disableVideoAutoplay = appSettings.disableVideoAutoplay.toBool(),
lowBandwidthMode = lowBandwidthMode,
enableInfiniteScroll = accountViewModel.currentAccount.value?.infiniteScrollEnabled ?: true
)
}

Expand Down Expand Up @@ -362,6 +364,7 @@ class MainActivity : AppCompatActivity() {
swipeToActionPreset = appSettings.swipeToActionPreset.toEnum(),
disableVideoAutoplay = appSettings.disableVideoAutoplay.toBool(),
lowBandwidthMode = lowBandwidthMode,
enableInfiniteScroll = accountViewModel.currentAccount.value?.infiniteScrollEnabled ?: true
)
}

Expand Down Expand Up @@ -403,6 +406,7 @@ class MainActivity : AppCompatActivity() {
swipeToActionPreset = appSettings.swipeToActionPreset.toEnum(),
disableVideoAutoplay = appSettings.disableVideoAutoplay.toBool(),
lowBandwidthMode = lowBandwidthMode,
enableInfiniteScroll = accountViewModel.currentAccount.value?.infiniteScrollEnabled ?: true
)
}

Expand Down
2 changes: 1 addition & 1 deletion app/src/main/java/com/jerboa/db/AppDB.kt
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ val APP_SETTINGS_DEFAULT =
)

@Database(
version = 35,
version = 36,
entities = [Account::class, AppSettings::class],
exportSchema = true,
)
Expand Down
10 changes: 10 additions & 0 deletions app/src/main/java/com/jerboa/db/AppDBMigrations.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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 Account ADD COLUMN infinite_scroll_enabled INTEGER NOT NULL DEFAULT 1",
)
}
}

// Don't forget to test your migration with `./gradlew app:connectAndroidTest`
val MIGRATIONS_LIST =
arrayOf(
Expand Down Expand Up @@ -612,4 +621,5 @@ val MIGRATIONS_LIST =
MIGRATION_32_33,
MIGRATION_33_34,
MIGRATION_34_35,
MIGRATION_35_36
)
6 changes: 6 additions & 0 deletions app/src/main/java/com/jerboa/db/entity/Account.kt
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ data class Account(
defaultValue = "0",
)
val defaultSortType: Int,
@ColumnInfo(
name = "infinite_scroll_enabled",
defaultValue = "1"
)
val infiniteScrollEnabled: Boolean,
Comment thread
rodrigo-fm marked this conversation as resolved.
@ColumnInfo(
name = "verification_state",
defaultValue = "0",
Expand All @@ -43,6 +48,7 @@ val AnonAccount =
defaultListingType = 1,
defaultSortType = 0,
verificationState = 0,
infiniteScrollEnabled = true,
isAdmin = false,
isMod = false,
)
Expand Down
34 changes: 26 additions & 8 deletions app/src/main/java/com/jerboa/feed/PaginationController.kt
Comment thread
rodrigo-fm marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -1,18 +1,36 @@
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

class PaginationController {

val previousPageCursors = Stack<PaginationCursor?>()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In 1.0, we won't need this stack, as the PagedResponse returns a prev_page and next_page, so please add a TODO comment about that.

var currentPageCursor: PaginationCursor? = null
var nextPageCursor: PaginationCursor? = null
var page by mutableLongStateOf(1)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did this need to change. I thought mutableStateOf only matter for functional components.


fun appendPage(nextPage: PaginationCursor?) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to be a dupe of nextPage but without pushing to the stack. Why is that necessary?

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()
}
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)
Expand Down
6 changes: 6 additions & 0 deletions app/src/main/java/com/jerboa/model/CommunityViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions app/src/main/java/com/jerboa/model/LoginViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
)

Expand Down
34 changes: 21 additions & 13 deletions app/src/main/java/com/jerboa/model/PersonProfileViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ class PersonProfileViewModel(
private var hidePostRes: ApiState<(Unit)> by mutableStateOf(ApiState.Empty)
private var lockPostRes: ApiState<PostResponse> by mutableStateOf(ApiState.Empty)
private var featurePostRes: ApiState<PostResponse> by mutableStateOf(ApiState.Empty)
private var blockCommunityRes: ApiState<BlockCommunityResponse> by mutableStateOf(ApiState.Empty)
private var blockPersonRes: ApiState<BlockPersonResponse> by mutableStateOf(ApiState.Empty)

private var likeCommentRes: ApiState<CommentResponse> by mutableStateOf(ApiState.Empty)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down
43 changes: 38 additions & 5 deletions app/src/main/java/com/jerboa/model/PostsViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -29,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
Expand All @@ -51,6 +51,9 @@ open class PostsViewModel(
private val pageController = PaginationController()
private val postController = PostController()

val currentPage: Long
get() = pageController.page
Comment on lines +54 to +55

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why. Just use the pageController.page.

@rodrigo-fm rodrigo-fm Jul 24, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The pageController is private and I was passing the currentPage as an argument to some screens (to display on the Paginator composable), but it might not be needed anymore after implementing all the changes suggested here.


protected fun init() {
viewModelScope.launch {
accountRepository.currentAccount
Expand All @@ -68,12 +71,14 @@ open class PostsViewModel(
private fun initPosts(
form: GetPosts,
state: ApiState<List<PostView>> = 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)
},
Expand All @@ -94,7 +99,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)
}
Expand All @@ -114,21 +119,49 @@ open class PostsViewModel(
postController.findAndUpdatePostCreatorBannedFromCommunity(banData)
}

fun nextPage() {
initPosts(
getForm(),
ApiState.Loading,
onSuccess = pageController::nextPage
)
}

fun previousPage() {
if (!pageController.canMoveToPreviousPage()) return

pageController.page--
pageController.currentPageCursor = pageController.previousPageCursors.pop()
Comment on lines +133 to +134

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not add these as functions to pageController, just like with the others.


initPosts(
getPreviousPageForm(),
ApiState.Loading,
onSuccess = { pageController.nextPageCursor = it },
)
}

fun resetPosts(state: ApiState<List<PostView>> = ApiState.Loading) {
pageController.reset()
postController.clear()
initPosts(
getForm(),
state,
onSuccess = { pageController.nextPageCursor = it }
)
}

fun refreshPosts() = resetPosts(ApiState.Refreshing)

protected open fun getPreviousPageForm() = GetPosts(
page = pageController.page,
page_cursor = pageController.currentPageCursor,
Comment on lines +154 to +156

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like the current page, not the previous one.

I don't think these extra form builders will be necessary in 1.0 also. It will be:

  • The next_page and prev_page are already in the PagedResponse, and given to the PageButton
  • User clicks next or previous on the page button, pushing up the correct page_cursor.
  • The viewmodel does the fetch given that page cursor, and sets next_page and prev_page again.
  • repeat

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,
)
Expand Down
1 change: 1 addition & 0 deletions app/src/main/java/com/jerboa/model/SiteViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -942,6 +942,10 @@ fun CommentNodesPreview() {
account = AnonAccount,
voteDisplayMode = LocalUserVoteDisplayMode.default(),
swipeToActionPreset = SwipeToActionPreset.TwoSides,
nextPage = { },
previousPage = { },
page = 2,
enableInfiniteScroll = false,
)
}

Expand Down
Loading