Add cache with last loaded statuses on home timeline

This commit is contained in:
Marcin Czachurski 2023-10-21 08:18:36 +02:00
parent 6c3bf3a0fc
commit f3598a9940
15 changed files with 68 additions and 41 deletions

View File

@ -36,6 +36,9 @@ import ClientKit
/// Last status loaded on home timeline.
public var lastLoadedStatusId: String?
/// JSON string with last objects loaded into home timeline.
public var timelineCache: String?
@Relationship(deleteRule: .cascade, inverse: \ViewedStatus.pixelfedAccount) public var viewedStatuses: [ViewedStatus]
@Relationship(deleteRule: .cascade, inverse: \AccountRelationship.pixelfedAccount) public var accountRelationships: [AccountRelationship]

View File

@ -6,6 +6,7 @@
import Foundation
import SwiftData
import PixelfedKit
class AccountDataHandler {
public static let shared = AccountDataHandler()
@ -62,7 +63,7 @@ class AccountDataHandler {
}
}
func update(lastSeenStatusId: String?, lastLoadedStatusId: String?, accountId: String, modelContext: ModelContext) throws {
func update(lastSeenStatusId: String?, lastLoadedStatusId: String?, statuses: [Status]? = nil, accountId: String, modelContext: ModelContext) throws {
guard let accountDataFromDb = self.getAccountData(accountId: accountId, modelContext: modelContext) else {
return
}
@ -75,6 +76,10 @@ class AccountDataHandler {
accountDataFromDb.lastLoadedStatusId = lastLoadedStatusId
}
if let statuses, let statusesJsonData = try? JSONEncoder().encode(statuses) {
accountDataFromDb.timelineCache = String(data: statusesJsonData, encoding: .utf8)
}
try modelContext.save()
}
}

View File

@ -106,13 +106,14 @@ public class HomeTimelineService {
return visibleStatuses
}
public func update(lastSeenStatusId: String?, lastLoadedStatusId: String?, applicationState: ApplicationState, modelContext: ModelContext) throws {
public func update(lastSeenStatusId: String?, lastLoadedStatusId: String?, statuses: [Status]? = nil, applicationState: ApplicationState, modelContext: ModelContext) throws {
guard let accountId = applicationState.account?.id else {
return
}
try AccountDataHandler.shared.update(lastSeenStatusId: lastSeenStatusId,
lastLoadedStatusId: lastLoadedStatusId,
statuses: statuses,
accountId: accountId,
modelContext: modelContext)

View File

@ -100,7 +100,10 @@ struct AccountsPhotoView: View {
private func loadData() async {
do {
self.accounts = try await self.loadAccounts()
self.state = .loaded
withAnimation {
self.state = .loaded
}
} catch {
if !Task.isCancelled {
ErrorService.shared.handle(error, message: "trendingAccounts.error.loadingAccountsFailed", showToastr: true)

View File

@ -120,7 +120,10 @@ struct AccountsView: View {
private func loadData(page: Int) async {
do {
try await self.loadAccounts(page: page)
self.state = .loaded
withAnimation {
self.state = .loaded
}
} catch {
if !Task.isCancelled {
ErrorService.shared.handle(error, message: "accounts.error.loadingAccountsFailed", showToastr: true)

View File

@ -127,7 +127,10 @@ struct FollowRequestsView: View {
private func loadData(page: Int) async {
do {
try await self.loadAccounts(page: page)
self.state = .loaded
withAnimation {
self.state = .loaded
}
} catch {
if !Task.isCancelled {
ErrorService.shared.handle(error, message: "accounts.error.loadingAccountsFailed", showToastr: true)

View File

@ -99,7 +99,10 @@ struct HashtagsView: View {
private func loadData() async {
do {
self.tags = try await self.loadTags()
self.state = .loaded
withAnimation {
self.state = .loaded
}
} catch {
if !Task.isCancelled {
ErrorService.shared.handle(error, message: "tags.error.loadingTagsFailed", showToastr: true)

View File

@ -160,7 +160,9 @@ struct HomeTimelineView: View {
try await self.loadFirstStatuses()
try ViewedStatusHandler.shared.deleteOldViewedStatuses(modelContext: modelContext)
self.state = .loaded
withAnimation {
self.state = .loaded
}
} catch {
ErrorService.shared.handle(error, message: "statuses.error.loadingStatusesFailed", showToastr: !Task.isCancelled)
self.state = .error(error)
@ -173,11 +175,8 @@ struct HomeTimelineView: View {
return
}
// We have to download one newer status Id.
let newerStatusId = try await self.getNewerStatusId(from: accountData.lastLoadedStatusId)
// Download statuses from API (which are older then last visible status).
let statuses = try await self.loadFromApi(maxId: newerStatusId)
let statuses = try await self.loadFromCacheOrApi(timelineCache: accountData.timelineCache)
if statuses.isEmpty {
self.allItemsLoaded = true
@ -273,6 +272,7 @@ struct HomeTimelineView: View {
// Remeber first status returned by API in user context (when it's newer then remembered).
try HomeTimelineService.shared.update(lastSeenStatusId: self.statusViewModels.first?.id,
lastLoadedStatusId: statuses.first?.id,
statuses: statuses,
applicationState: self.applicationState,
modelContext: modelContext)
@ -292,6 +292,17 @@ struct HomeTimelineView: View {
self.applicationState.amountOfNewStatuses = 0
}
private func loadFromCacheOrApi(timelineCache: String?) async throws -> [Status] {
if let timelineCache, let timelineCacheData = timelineCache.data(using: .utf8) {
let statusesFromCache = try? JSONDecoder().decode([Status].self, from: timelineCacheData)
if let statusesFromCache {
return statusesFromCache
}
}
return try await self.loadFromApi()
}
private func loadFromApi(maxId: String? = nil, sinceId: String? = nil, minId: String? = nil) async throws -> [Status] {
return try await self.client.publicTimeline?.getHomeTimeline(
maxId: maxId,
@ -338,17 +349,4 @@ struct HomeTimelineView: View {
private func shouldUpToDateBeVisible(statusId: String) -> Bool {
return self.applicationState.lastSeenStatusId != statusViewModels.first?.id && self.applicationState.lastSeenStatusId == statusId
}
private func getNewerStatusId(from statusId: String?) async throws -> String? {
guard let statusId else {
return nil
}
let statuses = try await self.client.publicTimeline?.getHomeTimeline(
minId: statusId,
limit: 1,
includeReblogs: self.applicationState.showReboostedStatuses) ?? []
return statuses.first?.id
}
}

View File

@ -131,7 +131,9 @@ struct InstanceView: View {
self.instance = try await self.client.instances.instance(url: serverUrl)
}
self.state = .loaded
withAnimation {
self.state = .loaded
}
} catch {
if !Task.isCancelled {
ErrorService.shared.handle(error, message: "instance.error.loadingDataFailed", showToastr: true)

View File

@ -92,7 +92,9 @@ struct NotificationsView: View {
self.allItemsLoaded = true
}
self.state = .loaded
withAnimation {
self.state = .loaded
}
}
} catch {
if !Task.isCancelled {

View File

@ -124,7 +124,10 @@ struct PaginableStatusesView: View {
private func loadData() async {
do {
try await self.loadStatuses()
self.state = .loaded
withAnimation {
self.state = .loaded
}
} catch {
ErrorService.shared.handle(error, message: "statuses.error.loadingStatusesFailed", showToastr: !Task.isCancelled)
self.state = .error(error)

View File

@ -201,20 +201,13 @@ struct StatusView: View {
}
self.statusViewModel = statusModel
// If we have status in database then we can update data.
// TODO: It seems that Pixelfed didn't support status edit, thus we don't need to update status.
/*
if let accountData = self.applicationState.account,
let statusDataFromDatabase = StatusDataHandler.shared.getStatusData(accountId: accountData.id, statusId: self.statusId) {
_ = try await HomeTimelineService.shared.update(status: statusDataFromDatabase, basedOn: status, for: accountData)
}
*/
}
self.state = .loaded
withAnimation {
self.state = .loaded
}
} catch NetworkError.notSuccessResponse(let response) {
if response.statusCode() == HTTPStatusCode.notFound, let accountId = self.applicationState.account?.id {
if response.statusCode() == HTTPStatusCode.notFound {
ErrorService.shared.handle(NetworkError.notSuccessResponse(response), message: "status.error.notFound", showToastr: true)
self.dismiss()
}

View File

@ -166,7 +166,9 @@ struct StatusesView: View {
await self.loadTag(hashtag: hashtag)
}
self.state = .loaded
withAnimation {
self.state = .loaded
}
} catch {
ErrorService.shared.handle(error, message: "statuses.error.loadingStatusesFailed", showToastr: !Task.isCancelled)
self.state = .error(error)

View File

@ -128,12 +128,16 @@ struct UserProfileStatusesView: View {
// Prefetch images.
self.prefetch(statusModels: inPlaceStatuses)
self.firstLoadFinished = true
// Append downloaded statuses to the list.
self.statusViewModels.append(contentsOf: inPlaceStatuses)
if statuses.count < self.defaultLimit {
self.allItemsLoaded = true
}
withAnimation {
self.firstLoadFinished = true
}
}
private func loadMoreStatuses() async throws {

View File

@ -128,7 +128,9 @@ struct UserProfileView: View {
self.account = accountFromApi
self.state = .loaded
withAnimation {
self.state = .loaded
}
} catch {
ErrorService.shared.handle(error, message: "userProfile.error.loadingAccountFailed", showToastr: !Task.isCancelled)
self.state = .error(error)