Added infinite scrolling to BookmarksView with dynamic loading of bookmarks on
This commit is contained in:
parent
789d581705
commit
601f81dc9f
@ -174,7 +174,10 @@
|
|||||||
},
|
},
|
||||||
"subpath" : "Yams"
|
"subpath" : "Yams"
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"prebuilts" : [
|
||||||
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"version" : 6
|
"version" : 7
|
||||||
}
|
}
|
||||||
@ -10,7 +10,7 @@ import Foundation
|
|||||||
protocol PAPI {
|
protocol PAPI {
|
||||||
var tokenProvider: TokenProvider { get }
|
var tokenProvider: TokenProvider { get }
|
||||||
func login(username: String, password: String) async throws -> UserDto
|
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 getBookmark(id: String) async throws -> BookmarkDetailDto
|
||||||
func getBookmarkArticle(id: String) async throws -> String
|
func getBookmarkArticle(id: String) async throws -> String
|
||||||
func createBookmark(createRequest: CreateBookmarkRequestDto) async throws -> CreateBookmarkResponseDto
|
func createBookmark(createRequest: CreateBookmarkRequestDto) async throws -> CreateBookmarkResponseDto
|
||||||
@ -131,21 +131,39 @@ class API: PAPI {
|
|||||||
return userDto
|
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 endpoint = "/api/bookmarks"
|
||||||
|
var queryItems: [URLQueryItem] = []
|
||||||
|
|
||||||
// Query-Parameter basierend auf State hinzufügen
|
// Query-Parameter basierend auf State hinzufügen
|
||||||
if let state = state {
|
if let state = state {
|
||||||
switch state {
|
switch state {
|
||||||
case .unread:
|
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:
|
case .favorite:
|
||||||
endpoint += "?is_marked=true"
|
queryItems.append(URLQueryItem(name: "is_marked", value: "true"))
|
||||||
case .archived:
|
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(
|
return try await makeJSONRequest(
|
||||||
endpoint: endpoint,
|
endpoint: endpoint,
|
||||||
responseType: [BookmarkDto].self
|
responseType: [BookmarkDto].self
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
protocol PBookmarksRepository {
|
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 fetchBookmark(id: String) async throws -> BookmarkDetail
|
||||||
func fetchBookmarkArticle(id: String) async throws -> String
|
func fetchBookmarkArticle(id: String) async throws -> String
|
||||||
func createBookmark(createRequest: CreateBookmarkRequest) async throws -> String
|
func createBookmark(createRequest: CreateBookmarkRequest) async throws -> String
|
||||||
@ -16,8 +16,8 @@ class BookmarksRepository: PBookmarksRepository {
|
|||||||
self.api = api
|
self.api = api
|
||||||
}
|
}
|
||||||
|
|
||||||
func fetchBookmarks(state: BookmarkState? = nil) async throws -> [Bookmark] {
|
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)
|
let bookmarkDtos = try await api.getBookmarks(state: state, limit: limit, offset: offset, search: search)
|
||||||
return bookmarkDtos.map { $0.toDomain() }
|
return bookmarkDtos.map { $0.toDomain() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -7,8 +7,8 @@ class GetBookmarksUseCase {
|
|||||||
self.repository = repository
|
self.repository = repository
|
||||||
}
|
}
|
||||||
|
|
||||||
func execute(state: BookmarkState? = nil) async throws -> [Bookmark] {
|
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)
|
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
|
// Fallback-Filterung auf Client-Seite falls API keine Query-Parameter unterstützt
|
||||||
if let state = state {
|
if let state = state {
|
||||||
|
|||||||
@ -40,6 +40,13 @@ struct BookmarksView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
.onAppear {
|
||||||
|
if bookmark.id == viewModel.bookmarks.last?.id {
|
||||||
|
Task {
|
||||||
|
await viewModel.loadMoreBookmarks()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.buttonStyle(PlainButtonStyle())
|
.buttonStyle(PlainButtonStyle())
|
||||||
.listRowInsets(EdgeInsets(top: 8, leading: 16, bottom: 8, trailing: 16))
|
.listRowInsets(EdgeInsets(top: 8, leading: 16, bottom: 8, trailing: 16))
|
||||||
@ -60,6 +67,7 @@ struct BookmarksView: View {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// FAB Button - nur bei "Ungelesen" anzeigen
|
// FAB Button - nur bei "Ungelesen" anzeigen
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import Combine
|
import Combine
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
@Observable
|
@Observable
|
||||||
class BookmarksViewModel {
|
class BookmarksViewModel {
|
||||||
@ -18,6 +19,11 @@ class BookmarksViewModel {
|
|||||||
|
|
||||||
private var cancellables = Set<AnyCancellable>()
|
private var cancellables = Set<AnyCancellable>()
|
||||||
|
|
||||||
|
// Pagination-Variablen
|
||||||
|
private var limit = 20
|
||||||
|
private var offset = 0
|
||||||
|
private var hasMoreData = true
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
setupNotificationObserver()
|
setupNotificationObserver()
|
||||||
}
|
}
|
||||||
@ -52,12 +58,35 @@ class BookmarksViewModel {
|
|||||||
isLoading = true
|
isLoading = true
|
||||||
errorMessage = nil
|
errorMessage = nil
|
||||||
currentState = state
|
currentState = state
|
||||||
|
offset = 0 // Offset zurücksetzen
|
||||||
|
hasMoreData = true // Pagination zurücksetzen
|
||||||
|
|
||||||
do {
|
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 {
|
} catch {
|
||||||
errorMessage = "Fehler beim Laden der Bookmarks"
|
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
|
isLoading = false
|
||||||
|
|||||||
7
readeck/UI/Bookmarks/File.swift
Normal file
7
readeck/UI/Bookmarks/File.swift
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
//
|
||||||
|
// File.swift
|
||||||
|
// readeck
|
||||||
|
//
|
||||||
|
// Created by Ilyas Hallak on 25.06.25.
|
||||||
|
//
|
||||||
|
|
||||||
Loading…
x
Reference in New Issue
Block a user