Added infinite scrolling to BookmarksView with dynamic loading of bookmarks on

This commit is contained in:
Ilyas Hallak 2025-06-25 22:39:02 +02:00
parent 789d581705
commit 601f81dc9f
7 changed files with 78 additions and 13 deletions

View File

@ -174,7 +174,10 @@
},
"subpath" : "Yams"
}
],
"prebuilts" : [
]
},
"version" : 6
"version" : 7
}

View File

@ -10,7 +10,7 @@ import Foundation
protocol PAPI {
var tokenProvider: TokenProvider { get }
func login(username: String, password: String) async throws -> UserDto
func getBookmarks(state: BookmarkState?) async throws -> [BookmarkDto]
func getBookmarks(state: BookmarkState?, limit: Int?, offset: Int?, search: String?) async throws -> [BookmarkDto]
func getBookmark(id: String) async throws -> BookmarkDetailDto
func getBookmarkArticle(id: String) async throws -> String
func createBookmark(createRequest: CreateBookmarkRequestDto) async throws -> CreateBookmarkResponseDto
@ -131,21 +131,39 @@ class API: PAPI {
return userDto
}
func getBookmarks(state: BookmarkState? = nil) async throws -> [BookmarkDto] {
func getBookmarks(state: BookmarkState? = nil, limit: Int? = nil, offset: Int? = nil, search: String? = nil) async throws -> [BookmarkDto] {
var endpoint = "/api/bookmarks"
var queryItems: [URLQueryItem] = []
// Query-Parameter basierend auf State hinzufügen
if let state = state {
switch state {
case .unread:
endpoint += "?is_archived=false&is_marked=false"
queryItems.append(URLQueryItem(name: "is_archived", value: "false"))
queryItems.append(URLQueryItem(name: "is_marked", value: "false"))
case .favorite:
endpoint += "?is_marked=true"
queryItems.append(URLQueryItem(name: "is_marked", value: "true"))
case .archived:
endpoint += "?is_archived=true"
queryItems.append(URLQueryItem(name: "is_archived", value: "true"))
}
}
if let limit = limit {
queryItems.append(URLQueryItem(name: "limit", value: "\(limit)"))
}
if let offset = offset {
queryItems.append(URLQueryItem(name: "offset", value: "\(offset)"))
}
if let search = search {
queryItems.append(URLQueryItem(name: "search", value: search))
}
if !queryItems.isEmpty {
let queryString = queryItems.map { "\($0.name)=\($0.value ?? "")" }.joined(separator: "&")
endpoint += "?\(queryString)"
}
return try await makeJSONRequest(
endpoint: endpoint,
responseType: [BookmarkDto].self

View File

@ -1,7 +1,7 @@
import Foundation
protocol PBookmarksRepository {
func fetchBookmarks(state: BookmarkState?) async throws -> [Bookmark]
func fetchBookmarks(state: BookmarkState?, limit: Int?, offset: Int?, search: String?) async throws -> [Bookmark]
func fetchBookmark(id: String) async throws -> BookmarkDetail
func fetchBookmarkArticle(id: String) async throws -> String
func createBookmark(createRequest: CreateBookmarkRequest) async throws -> String
@ -16,8 +16,8 @@ class BookmarksRepository: PBookmarksRepository {
self.api = api
}
func fetchBookmarks(state: BookmarkState? = nil) async throws -> [Bookmark] {
let bookmarkDtos = try await api.getBookmarks(state: state)
func fetchBookmarks(state: BookmarkState? = nil, limit: Int? = nil, offset: Int? = nil, search: String? = nil) async throws -> [Bookmark] {
let bookmarkDtos = try await api.getBookmarks(state: state, limit: limit, offset: offset, search: search)
return bookmarkDtos.map { $0.toDomain() }
}

View File

@ -7,8 +7,8 @@ class GetBookmarksUseCase {
self.repository = repository
}
func execute(state: BookmarkState? = nil) async throws -> [Bookmark] {
let allBookmarks = try await repository.fetchBookmarks(state: state)
func execute(state: BookmarkState? = nil, limit: Int? = nil, offset: Int? = nil, search: String? = nil) async throws -> [Bookmark] {
let allBookmarks = try await repository.fetchBookmarks(state: state, limit: limit, offset: offset, search: search)
// Fallback-Filterung auf Client-Seite falls API keine Query-Parameter unterstützt
if let state = state {

View File

@ -40,6 +40,13 @@ struct BookmarksView: View {
}
}
)
.onAppear {
if bookmark.id == viewModel.bookmarks.last?.id {
Task {
await viewModel.loadMoreBookmarks()
}
}
}
}
.buttonStyle(PlainButtonStyle())
.listRowInsets(EdgeInsets(top: 8, leading: 16, bottom: 8, trailing: 16))
@ -60,6 +67,7 @@ struct BookmarksView: View {
)
}
}
}
// FAB Button - nur bei "Ungelesen" anzeigen

View File

@ -1,5 +1,6 @@
import Foundation
import Combine
import SwiftUI
@Observable
class BookmarksViewModel {
@ -18,6 +19,11 @@ class BookmarksViewModel {
private var cancellables = Set<AnyCancellable>()
// Pagination-Variablen
private var limit = 20
private var offset = 0
private var hasMoreData = true
init() {
setupNotificationObserver()
}
@ -52,12 +58,35 @@ class BookmarksViewModel {
isLoading = true
errorMessage = nil
currentState = state
offset = 0 // Offset zurücksetzen
hasMoreData = true // Pagination zurücksetzen
do {
bookmarks = try await getBooksmarksUseCase.execute(state: state)
let newBookmarks = try await getBooksmarksUseCase.execute(state: state, limit: limit, offset: offset)
bookmarks = newBookmarks
hasMoreData = newBookmarks.count == limit // Prüfen, ob weitere Daten verfügbar sind
} catch {
errorMessage = "Fehler beim Laden der Bookmarks"
bookmarks = []
bookmarks = []
}
isLoading = false
}
@MainActor
func loadMoreBookmarks() async {
guard !isLoading && hasMoreData else { return } // Verhindern, dass mehrfach geladen wird
isLoading = true
errorMessage = nil
do {
offset += limit // Offset erhöhen
let newBookmarks = try await getBooksmarksUseCase.execute(state: currentState, limit: limit, offset: offset)
bookmarks.append(contentsOf: newBookmarks) // Neue Bookmarks hinzufügen
hasMoreData = newBookmarks.count == limit // Prüfen, ob weitere Daten verfügbar sind
} catch {
errorMessage = "Fehler beim Nachladen der Bookmarks"
}
isLoading = false

View File

@ -0,0 +1,7 @@
//
// File.swift
// readeck
//
// Created by Ilyas Hallak on 25.06.25.
//