Some improvements.

This commit is contained in:
Marcin Czachursk 2023-02-01 20:01:18 +01:00
parent b9e130e15b
commit 9fbd39657e
14 changed files with 251 additions and 54 deletions

View File

@ -135,12 +135,13 @@ public extension MastodonClientAuthenticated {
}
func favourites(maxId: EntityId? = nil,
sinceId: EntityId? = nil,
minId: EntityId? = nil,
limit: Int? = nil) async throws -> [Status] {
sinceId: EntityId? = nil,
minId: EntityId? = nil,
limit: Int? = nil,
page: Page? = nil) async throws -> [Status] {
let request = try Self.request(
for: baseURL,
target: Mastodon.Favourites.favourites(maxId, sinceId, minId, limit),
target: Mastodon.Favourites.favourites(maxId, sinceId, minId, limit, page),
withBearerToken: token
)
@ -150,10 +151,11 @@ public extension MastodonClientAuthenticated {
func bookmarks(maxId: EntityId? = nil,
sinceId: EntityId? = nil,
minId: EntityId? = nil,
limit: Int? = nil) async throws -> [Status] {
limit: Int? = nil,
page: Page? = nil) async throws -> [Status] {
let request = try Self.request(
for: baseURL,
target: Mastodon.Bookmarks.bookmarks(maxId, sinceId, minId, limit),
target: Mastodon.Bookmarks.bookmarks(maxId, sinceId, minId, limit, page),
withBearerToken: token
)

View File

@ -99,7 +99,7 @@ public class MastodonClientAuthenticated: MastodonClientProtocol {
public func downloadJson<T>(_ type: T.Type, request: URLRequest) async throws -> T where T: Decodable {
let (data, response) = try await urlSession.data(for: request)
guard (response as? HTTPURLResponse)?.status?.responseType == .success else {
throw NetworkError.notSuccessResponse(response)
}

View File

@ -8,7 +8,7 @@ import Foundation
extension Mastodon {
public enum Bookmarks {
case bookmarks(MaxId?, SinceId?, MinId?, Limit?)
case bookmarks(MaxId?, SinceId?, MinId?, Limit?, Page?)
}
}
@ -18,7 +18,7 @@ extension Mastodon.Bookmarks: TargetType {
/// The path to be appended to `baseURL` to form the full `URL`.
public var path: String {
switch self {
case .bookmarks(_, _, _, _):
case .bookmarks(_, _, _, _, _):
return "\(apiPath)"
}
}
@ -39,28 +39,37 @@ extension Mastodon.Bookmarks: TargetType {
var sinceId: SinceId? = nil
var minId: MinId? = nil
var limit: Limit? = nil
var page: Page? = nil
switch self {
case .bookmarks(let _maxId, let _sinceId, let _minId, let _limit):
case .bookmarks(let _maxId, let _sinceId, let _minId, let _limit, let _page):
maxId = _maxId
sinceId = _sinceId
minId = _minId
limit = _limit
page = _page
}
if let maxId {
params.append(("max_id", maxId))
}
if let sinceId {
params.append(("since_id", sinceId))
}
if let minId {
params.append(("min_id", minId))
}
if let limit {
params.append(("limit", "\(limit)"))
}
if let page {
params.append(("page", "\(page)"))
}
return params
}

View File

@ -8,7 +8,7 @@ import Foundation
extension Mastodon {
public enum Favourites {
case favourites(MaxId?, SinceId?, MinId?, Limit?)
case favourites(MaxId?, SinceId?, MinId?, Limit?, Page?)
}
}
@ -18,7 +18,7 @@ extension Mastodon.Favourites: TargetType {
/// The path to be appended to `baseURL` to form the full `URL`.
public var path: String {
switch self {
case .favourites(_, _, _, _):
case .favourites(_, _, _, _, _):
return "\(apiPath)"
}
}
@ -39,28 +39,37 @@ extension Mastodon.Favourites: TargetType {
var sinceId: SinceId? = nil
var minId: MinId? = nil
var limit: Limit? = nil
var page: Page? = nil
switch self {
case .favourites(let _maxId, let _sinceId, let _minId, let _limit):
case .favourites(let _maxId, let _sinceId, let _minId, let _limit, let _page):
maxId = _maxId
sinceId = _sinceId
minId = _minId
limit = _limit
page = _page
}
if let maxId {
params.append(("max_id", maxId))
}
if let sinceId {
params.append(("since_id", sinceId))
}
if let minId {
params.append(("min_id", minId))
}
if let limit {
params.append(("limit", "\(limit)"))
}
if let page {
params.append(("page", "\(page)"))
}
return params
}

View File

@ -54,3 +54,7 @@ In the status JSON we don't have information about bookmark status.
- ** Endpoint about instance information returns different JSON structure**
API in Pixelfed (`/api/v1/instance`) returns JSON with diefferent structure then API specify.
- **There are some issues in bookmarks/favourites endpoints**
It seems like paging is not working in that endpoints (I've tried with page and max_id).

View File

@ -66,6 +66,7 @@
F8764187298ABB520057D362 /* ViewState.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8764186298ABB520057D362 /* ViewState.swift */; };
F8764189298ABEC80057D362 /* ErrorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8764188298ABEC80057D362 /* ErrorView.swift */; };
F876418B298AC1B80057D362 /* NoDataView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F876418A298AC1B80057D362 /* NoDataView.swift */; };
F876418D298AE5020057D362 /* PaginableStatusesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F876418C298AE5020057D362 /* PaginableStatusesView.swift */; };
F87AEB922986C44E00434FB6 /* AuthorizationSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = F87AEB912986C44E00434FB6 /* AuthorizationSession.swift */; };
F87AEB942986C51B00434FB6 /* AppConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = F87AEB932986C51B00434FB6 /* AppConstants.swift */; };
F87AEB972986D16D00434FB6 /* AuthorisationError.swift in Sources */ = {isa = PBXBuildFile; fileRef = F87AEB962986D16D00434FB6 /* AuthorisationError.swift */; };
@ -186,6 +187,7 @@
F8764186298ABB520057D362 /* ViewState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewState.swift; sourceTree = "<group>"; };
F8764188298ABEC80057D362 /* ErrorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorView.swift; sourceTree = "<group>"; };
F876418A298AC1B80057D362 /* NoDataView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoDataView.swift; sourceTree = "<group>"; };
F876418C298AE5020057D362 /* PaginableStatusesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaginableStatusesView.swift; sourceTree = "<group>"; };
F87AEB912986C44E00434FB6 /* AuthorizationSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthorizationSession.swift; sourceTree = "<group>"; };
F87AEB932986C51B00434FB6 /* AppConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppConstants.swift; sourceTree = "<group>"; };
F87AEB962986D16D00434FB6 /* AuthorisationError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthorisationError.swift; sourceTree = "<group>"; };
@ -313,6 +315,7 @@
F88ABD9329687CA4004EF61E /* ComposeView.swift */,
F89A46DB296EAACE0062125F /* SettingsView.swift */,
F88E4D41297E69FD0057491A /* StatusesView.swift */,
F876418C298AE5020057D362 /* PaginableStatusesView.swift */,
F88E4D47297E90CD0057491A /* TrendStatusesView.swift */,
);
path = Views;
@ -714,6 +717,7 @@
F866F6A529604194002E8F88 /* ApplicationSettingsHandler.swift in Sources */,
F88ABD9229686F1C004EF61E /* MemoryCache.swift in Sources */,
F857F9FD297D8ED3002C109C /* ActionMenu.swift in Sources */,
F876418D298AE5020057D362 /* PaginableStatusesView.swift in Sources */,
F85D49852964301800751DF7 /* StatusData+Attachments.swift in Sources */,
F8764187298ABB520057D362 /* ViewState.swift in Sources */,
F8210DE72966E1D1001D9973 /* Color+Assets.swift in Sources */,

View File

@ -21,6 +21,10 @@ extension View {
imageHeight: metaImageHeight)
case .statuses(let listType):
StatusesView(listType: listType)
case .bookmarks:
PaginableStatusesView(listType: .bookmarks)
case .favourites:
PaginableStatusesView(listType: .favourites)
case .userProfile(let accountId, let accountDisplayName, let accountUserName):
UserProfileView(
accountId: accountId,

View File

@ -127,25 +127,27 @@ public class AccountService {
maxId: String? = nil,
sinceId: String? = nil,
minId: String? = nil,
limit: Int = 40) async throws -> [Status] {
limit: Int = 10,
page: Int? = nil) async throws -> [Status] {
guard let accessToken = account?.accessToken, let serverUrl = account?.serverUrl else {
return []
}
let client = MastodonClient(baseURL: serverUrl).getAuthenticated(token: accessToken)
return try await client.favourites(maxId: maxId, sinceId: sinceId, minId: minId, limit: limit)
return try await client.favourites(limit: limit, page: page)
}
public func bookmarks(for account: AccountModel?,
maxId: String? = nil,
sinceId: String? = nil,
minId: String? = nil,
limit: Int = 40) async throws -> [Status] {
limit: Int = 10,
page: Int? = nil) async throws -> [Status] {
guard let accessToken = account?.accessToken, let serverUrl = account?.serverUrl else {
return []
}
let client = MastodonClient(baseURL: serverUrl).getAuthenticated(token: accessToken)
return try await client.bookmarks(maxId: maxId, sinceId: sinceId, minId: minId, limit: limit)
return try await client.bookmarks(limit: limit, page: page)
}
}

View File

@ -12,6 +12,8 @@ enum RouteurDestinations: Hashable {
case tag(hashTag: String)
case status(id: String, blurhash: String? = nil, highestImageUrl: URL? = nil, metaImageWidth: Int32? = nil, metaImageHeight: Int32? = nil)
case statuses(listType: StatusesView.ListType)
case bookmarks
case favourites
case userProfile(accountId: String, accountDisplayName: String?, accountUserName: String)
case accounts(entityId: String, listType: AccountsView.ListType)
case signIn

View File

@ -88,8 +88,10 @@ struct AccountsView: View {
private func loadAccounts(page: Int) async {
do {
let accountsFromApi = try await self.loadFromApi()
if accountsFromApi.isEmpty {
self.allItemsLoaded = true
return
}
await self.downloadAvatars(accounts: accountsFromApi)

View File

@ -102,12 +102,13 @@ struct NotificationsView: View {
maxId: self.maxId,
limit: self.defaultPageSize)
self.maxId = linkable.link?.maxId
self.notifications.append(contentsOf: linkable.data)
if linkable.data.isEmpty {
self.allItemsLoaded = true
return
}
self.maxId = linkable.link?.maxId
self.notifications.append(contentsOf: linkable.data)
} catch {
ErrorService.shared.handle(error, message: "Error during download notifications from server.", showToastr: !Task.isCancelled)
}

View File

@ -0,0 +1,160 @@
//
// https://mczachurski.dev
// Copyright © 2022 Marcin Czachurski and the repository contributors.
// Licensed under the MIT License.
//
import SwiftUI
import MastodonKit
struct PaginableStatusesView: View {
public enum ListType: Hashable {
case favourites
case bookmarks
}
@EnvironmentObject private var applicationState: ApplicationState
@EnvironmentObject private var routerPath: RouterPath
@State public var listType: ListType
@State private var allItemsLoaded = false
@State private var statusViewModels: [StatusModel] = []
@State private var state: ViewState = .loading
@State private var page = 1
private let defaultLimit = 10
var body: some View {
self.mainBody()
.navigationBarTitle(self.getTitle())
}
@ViewBuilder
private func mainBody() -> some View {
switch state {
case .loading:
LoadingIndicator()
.task {
await self.loadData()
}
case .loaded:
if self.statusViewModels.isEmpty {
NoDataView(imageSystemName: "photo.on.rectangle.angled", text: "Unfortunately, there are no photos here.")
} else {
ScrollView {
LazyVStack(alignment: .center) {
ForEach(self.statusViewModels, id: \.id) { item in
NavigationLink(value: RouteurDestinations.status(
id: item.id,
blurhash: item.mediaAttachments.first?.blurhash,
highestImageUrl: item.mediaAttachments.getHighestImage()?.url,
metaImageWidth: item.getImageWidth(),
metaImageHeight: item.getImageHeight())
) {
ImageRowAsync(statusViewModel: item)
}
.buttonStyle(EmptyButtonStyle())
}
if allItemsLoaded == false {
HStack {
Spacer()
LoadingIndicator()
.task {
do {
try await self.loadMoreStatuses()
} catch {
ErrorService.shared.handle(error, message: "Loading more statuses failed.", showToastr: !Task.isCancelled)
}
}
Spacer()
}
}
}
}
}
case .error(let error):
ErrorView(error: error) {
self.state = .loading
self.page = 1
self.allItemsLoaded = false
await self.loadData()
}
.padding()
}
}
private func loadData() async {
do {
try await self.loadStatuses()
self.state = .loaded
} catch {
ErrorService.shared.handle(error, message: "Loading statuses failed.", showToastr: !Task.isCancelled)
self.state = .error(error)
}
}
private func loadStatuses() async throws {
let statuses = try await self.loadFromApi()
if statuses.isEmpty {
self.allItemsLoaded = true
return
}
// TODO: It seems that paging is not supported and we cannot download additiona data.
self.allItemsLoaded = true
var inPlaceStatuses: [StatusModel] = []
for item in statuses.getStatusesWithImagesOnly() {
inPlaceStatuses.append(StatusModel(status: item))
}
self.statusViewModels.append(contentsOf: inPlaceStatuses)
}
private func loadMoreStatuses() async throws {
self.page = self.page + 1
let previousStatuses = try await self.loadFromApi()
if previousStatuses.isEmpty {
self.allItemsLoaded = true
return
}
var inPlaceStatuses: [StatusModel] = []
for item in previousStatuses.getStatusesWithImagesOnly() {
inPlaceStatuses.append(StatusModel(status: item))
}
self.statusViewModels.append(contentsOf: inPlaceStatuses)
}
private func loadFromApi() async throws -> [Status] {
switch self.listType {
case .favourites:
return try await AccountService.shared.favourites(
for: self.applicationState.account,
limit: self.defaultLimit,
page: self.page)
case .bookmarks:
return try await AccountService.shared.bookmarks(
for: self.applicationState.account,
limit: self.defaultLimit,
page: self.page)
}
}
private func getTitle() -> String {
switch self.listType {
case .favourites:
return "Favourites"
case .bookmarks:
return "Bookmarks"
}
}
}

View File

@ -22,7 +22,6 @@ struct StatusesView: View {
@State public var listType: ListType
@State private var allItemsLoaded = false
@State private var firstLoadFinished = false
@State private var tag: Tag?
@State private var statusViewModels: [StatusModel] = []
@State private var state: ViewState = .loading
@ -51,34 +50,32 @@ struct StatusesView: View {
NoDataView(imageSystemName: "photo.on.rectangle.angled", text: "Unfortunately, there are no photos here.")
} else {
ScrollView {
if firstLoadFinished == true {
LazyVStack(alignment: .center) {
ForEach(self.statusViewModels, id: \.id) { item in
NavigationLink(value: RouteurDestinations.status(
id: item.id,
blurhash: item.mediaAttachments.first?.blurhash,
highestImageUrl: item.mediaAttachments.getHighestImage()?.url,
metaImageWidth: item.getImageWidth(),
metaImageHeight: item.getImageHeight())
) {
ImageRowAsync(statusViewModel: item)
}
.buttonStyle(EmptyButtonStyle())
LazyVStack(alignment: .center) {
ForEach(self.statusViewModels, id: \.id) { item in
NavigationLink(value: RouteurDestinations.status(
id: item.id,
blurhash: item.mediaAttachments.first?.blurhash,
highestImageUrl: item.mediaAttachments.getHighestImage()?.url,
metaImageWidth: item.getImageWidth(),
metaImageHeight: item.getImageHeight())
) {
ImageRowAsync(statusViewModel: item)
}
if allItemsLoaded == false && firstLoadFinished == true {
HStack {
Spacer()
LoadingIndicator()
.task {
do {
try await self.loadMoreStatuses()
} catch {
ErrorService.shared.handle(error, message: "Loading more statuses failed.", showToastr: !Task.isCancelled)
}
.buttonStyle(EmptyButtonStyle())
}
if allItemsLoaded == false {
HStack {
Spacer()
LoadingIndicator()
.task {
do {
try await self.loadMoreStatuses()
} catch {
ErrorService.shared.handle(error, message: "Loading more statuses failed.", showToastr: !Task.isCancelled)
}
Spacer()
}
}
Spacer()
}
}
}
@ -116,18 +113,18 @@ struct StatusesView: View {
}
private func loadStatuses() async throws {
guard firstLoadFinished == false else {
let statuses = try await self.loadFromApi()
if statuses.isEmpty {
self.allItemsLoaded = true
return
}
let statuses = try await self.loadFromApi()
var inPlaceStatuses: [StatusModel] = []
for item in statuses.getStatusesWithImagesOnly() {
inPlaceStatuses.append(StatusModel(status: item))
}
self.firstLoadFinished = true
self.statusViewModels.append(contentsOf: inPlaceStatuses)
}
@ -137,6 +134,7 @@ struct StatusesView: View {
if previousStatuses.isEmpty {
self.allItemsLoaded = true
return
}
var inPlaceStatuses: [StatusModel] = []

View File

@ -136,11 +136,11 @@ struct UserProfileView: View {
Divider()
}
NavigationLink(value: RouteurDestinations.statuses(listType: .favourites)) {
NavigationLink(value: RouteurDestinations.favourites) {
Label("Favourites", systemImage: "hand.thumbsup")
}
NavigationLink(value: RouteurDestinations.statuses(listType: .bookmarks)) {
NavigationLink(value: RouteurDestinations.bookmarks) {
Label("Bookmarks", systemImage: "bookmark")
}