Add favourites/bookmarks to profile

This commit is contained in:
Marcin Czachursk 2023-01-23 08:43:04 +01:00
parent 92c0bf75ce
commit 4358ed6a38
9 changed files with 231 additions and 56 deletions

View File

@ -133,4 +133,30 @@ public extension MastodonClientAuthenticated {
return try await downloadJson([Account].self, request: request)
}
func favourites(maxId: EntityId? = nil,
sinceId: EntityId? = nil,
minId: EntityId? = nil,
limit: Int? = nil) async throws -> [Status] {
let request = try Self.request(
for: baseURL,
target: Mastodon.Favourites.favourites(maxId, sinceId, minId, limit),
withBearerToken: token
)
return try await downloadJson([Status].self, request: request)
}
func bookmarks(maxId: EntityId? = nil,
sinceId: EntityId? = nil,
minId: EntityId? = nil,
limit: Int? = nil) async throws -> [Status] {
let request = try Self.request(
for: baseURL,
target: Mastodon.Bookmarks.bookmarks(maxId, sinceId, minId, limit),
withBearerToken: token
)
return try await downloadJson([Status].self, request: request)
}
}

View File

@ -0,0 +1,74 @@
//
// https://mczachurski.dev
// Copyright © 2022 Marcin Czachurski and the repository contributors.
// Licensed under the MIT License.
//
import Foundation
extension Mastodon {
public enum Bookmarks {
case bookmarks(MaxId?, SinceId?, MinId?, Limit?)
}
}
extension Mastodon.Bookmarks: TargetType {
fileprivate var apiPath: String { return "/api/v1/bookmarks" }
/// The path to be appended to `baseURL` to form the full `URL`.
public var path: String {
switch self {
case .bookmarks(_, _, _, _):
return "\(apiPath)"
}
}
/// The HTTP method used in the request.
public var method: Method {
switch self {
case .bookmarks:
return .get
}
}
/// The parameters to be incoded in the request.
public var queryItems: [(String, String)]? {
var params: [(String, String)] = []
var maxId: MaxId? = nil
var sinceId: SinceId? = nil
var minId: MinId? = nil
var limit: Limit? = nil
switch self {
case .bookmarks(let _maxId, let _sinceId, let _minId, let _limit):
maxId = _maxId
sinceId = _sinceId
minId = _minId
limit = _limit
}
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)"))
}
return params
}
public var headers: [String: String]? {
[:].contentTypeApplicationJson
}
public var httpBody: Data? {
nil
}
}

View File

@ -8,7 +8,7 @@ import Foundation
extension Mastodon {
public enum Favourites {
case favourites
case favourites(MaxId?, SinceId?, MinId?, Limit?)
}
}
@ -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)"
}
}
@ -33,10 +33,35 @@ extension Mastodon.Favourites: TargetType {
/// The parameters to be incoded in the request.
public var queryItems: [(String, String)]? {
var params: [(String, String)] = []
var maxId: MaxId? = nil
var sinceId: SinceId? = nil
var minId: MinId? = nil
var limit: Limit? = nil
switch self {
case .favourites:
return nil
case .favourites(let _maxId, let _sinceId, let _minId, let _limit):
maxId = _maxId
sinceId = _sinceId
minId = _minId
limit = _limit
}
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)"))
}
return params
}
public var headers: [String: String]? {

View File

@ -69,9 +69,8 @@
F88C2478295C37BB0006098B /* Vernissage.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = F88C2476295C37BB0006098B /* Vernissage.xcdatamodeld */; };
F88C2482295C3A4F0006098B /* StatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F88C2481295C3A4F0006098B /* StatusView.swift */; };
F88C2486295C48030006098B /* HTMLFotmattedText.swift in Sources */ = {isa = PBXBuildFile; fileRef = F88C2485295C48030006098B /* HTMLFotmattedText.swift */; };
F88E4D42297E69FD0057491A /* StatusesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F88E4D41297E69FD0057491A /* StatusesView.swift */; };
F88FAD21295F3944009B20C9 /* HomeFeedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F88FAD20295F3944009B20C9 /* HomeFeedView.swift */; };
F88FAD23295F3FC4009B20C9 /* TimelineFeedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F88FAD22295F3FC4009B20C9 /* TimelineFeedView.swift */; };
F88FAD25295F3FF7009B20C9 /* FederatedFeedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F88FAD24295F3FF7009B20C9 /* FederatedFeedView.swift */; };
F88FAD27295F400E009B20C9 /* NotificationsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F88FAD26295F400E009B20C9 /* NotificationsView.swift */; };
F88FAD2A295F43B8009B20C9 /* AccountData+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = F88FAD28295F43B8009B20C9 /* AccountData+CoreDataClass.swift */; };
F88FAD2B295F43B8009B20C9 /* AccountData+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = F88FAD29295F43B8009B20C9 /* AccountData+CoreDataProperties.swift */; };
@ -168,9 +167,8 @@
F88C2477295C37BB0006098B /* Vernissage.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Vernissage.xcdatamodel; sourceTree = "<group>"; };
F88C2481295C3A4F0006098B /* StatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusView.swift; sourceTree = "<group>"; };
F88C2485295C48030006098B /* HTMLFotmattedText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HTMLFotmattedText.swift; sourceTree = "<group>"; };
F88E4D41297E69FD0057491A /* StatusesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusesView.swift; sourceTree = "<group>"; };
F88FAD20295F3944009B20C9 /* HomeFeedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeFeedView.swift; sourceTree = "<group>"; };
F88FAD22295F3FC4009B20C9 /* TimelineFeedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineFeedView.swift; sourceTree = "<group>"; };
F88FAD24295F3FF7009B20C9 /* FederatedFeedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FederatedFeedView.swift; sourceTree = "<group>"; };
F88FAD26295F400E009B20C9 /* NotificationsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsView.swift; sourceTree = "<group>"; };
F88FAD28295F43B8009B20C9 /* AccountData+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AccountData+CoreDataClass.swift"; sourceTree = "<group>"; };
F88FAD29295F43B8009B20C9 /* AccountData+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AccountData+CoreDataProperties.swift"; sourceTree = "<group>"; };
@ -251,8 +249,6 @@
F88C2481295C3A4F0006098B /* StatusView.swift */,
F88C246D295C37B80006098B /* MainView.swift */,
F88FAD20295F3944009B20C9 /* HomeFeedView.swift */,
F88FAD22295F3FC4009B20C9 /* TimelineFeedView.swift */,
F88FAD24295F3FF7009B20C9 /* FederatedFeedView.swift */,
F88FAD26295F400E009B20C9 /* NotificationsView.swift */,
F866F6A629604629002E8F88 /* SignInView.swift */,
F8A93D7D2965FD89001D8331 /* UserProfileView.swift */,
@ -260,6 +256,7 @@
F897978E29684BCB00B22335 /* LoadingView.swift */,
F88ABD9329687CA4004EF61E /* ComposeView.swift */,
F89A46DB296EAACE0062125F /* SettingsView.swift */,
F88E4D41297E69FD0057491A /* StatusesView.swift */,
);
path = Views;
sourceTree = "<group>";
@ -584,7 +581,6 @@
F897978A2968314A00B22335 /* LoadingIndicator.swift in Sources */,
F8210DE52966E160001D9973 /* Color+SystemColors.swift in Sources */,
F85DBF93296760790069BF89 /* CacheAvatarService.swift in Sources */,
F88FAD23295F3FC4009B20C9 /* TimelineFeedView.swift in Sources */,
F88FAD2B295F43B8009B20C9 /* AccountData+CoreDataProperties.swift in Sources */,
F85D4975296407F100751DF7 /* HomeTimelineService.swift in Sources */,
F80048062961850500E6868A /* StatusData+CoreDataProperties.swift in Sources */,
@ -601,6 +597,7 @@
F85D498329642FAC00751DF7 /* AttachmentData+Comperable.swift in Sources */,
F85D497B29640C8200751DF7 /* UsernameRow.swift in Sources */,
F89D6C4429718092001DA3D4 /* AccentsSection.swift in Sources */,
F88E4D42297E69FD0057491A /* StatusesView.swift in Sources */,
F85D497929640B9D00751DF7 /* ImagesCarousel.swift in Sources */,
F89D6C3F29716E41001DA3D4 /* Theme.swift in Sources */,
F8CC95CE2970761D00C9C2AC /* TintColor.swift in Sources */,
@ -644,7 +641,6 @@
F88C246C295C37B80006098B /* VernissageApp.swift in Sources */,
F802884F297AEED5000BDD51 /* DatabaseError.swift in Sources */,
F85D4971296402DC00751DF7 /* AuthorizationService.swift in Sources */,
F88FAD25295F3FF7009B20C9 /* FederatedFeedView.swift in Sources */,
F88FAD32295F5029009B20C9 /* RemoteFileService.swift in Sources */,
F88FAD27295F400E009B20C9 /* NotificationsView.swift in Sources */,
F86B7216296BFFDA00EE59EC /* UserProfileStatuses.swift in Sources */,

View File

@ -122,4 +122,30 @@ public class AccountService {
let client = MastodonClient(baseURL: serverUrl).getAuthenticated(token: accessToken)
return try await client.following(for: accountId, page: page)
}
public func favourites(accountData: AccountData?,
maxId: String? = nil,
sinceId: String? = nil,
minId: String? = nil,
limit: Int = 40) async throws -> [Status] {
guard let accessToken = accountData?.accessToken, let serverUrl = accountData?.serverUrl else {
return []
}
let client = MastodonClient(baseURL: serverUrl).getAuthenticated(token: accessToken)
return try await client.favourites(maxId: maxId, sinceId: sinceId, minId: minId, limit: limit)
}
public func bookmarks(accountData: AccountData?,
maxId: String? = nil,
sinceId: String? = nil,
minId: String? = nil,
limit: Int = 40) async throws -> [Status] {
guard let accessToken = accountData?.accessToken, let serverUrl = accountData?.serverUrl else {
return []
}
let client = MastodonClient(baseURL: serverUrl).getAuthenticated(token: accessToken)
return try await client.bookmarks(maxId: maxId, sinceId: sinceId, minId: minId, limit: limit)
}
}

View File

@ -1,13 +0,0 @@
//
// https://mczachurski.dev
// Copyright © 2022 Marcin Czachurski and the repository contributors.
// Licensed under the MIT License.
//
import SwiftUI
struct FederatedFeedView: View {
var body: some View {
Text("Federated feed")
}
}

View File

@ -67,10 +67,10 @@ struct MainView: View {
HomeFeedView(accountId: applicationState.accountData?.id ?? String.empty())
.id(applicationState.accountData?.id ?? String.empty())
case .local:
TimelineFeedView(accountId: applicationState.accountData?.id ?? String.empty(), isLocalOnly: true)
StatusesView(accountId: applicationState.accountData?.id ?? String.empty(), listType: .local)
.id(applicationState.accountData?.id ?? String.empty())
case .federated:
TimelineFeedView(accountId: applicationState.accountData?.id ?? String.empty(), isLocalOnly: false)
StatusesView(accountId: applicationState.accountData?.id ?? String.empty(), listType: .federated)
.id(applicationState.accountData?.id ?? String.empty())
case .profile:
if let accountData = self.applicationState.accountData {

View File

@ -5,11 +5,19 @@
//
import SwiftUI
import MastodonKit
struct TimelineFeedView: View {
struct StatusesView: View {
public enum ListType {
case local
case federated
case favourites
case bookmarks
}
@EnvironmentObject private var applicationState: ApplicationState
@State public var accountId: String
@State public var isLocalOnly: Bool
@State public var listType: ListType
@State private var allItemsLoaded = false
@State private var firstLoadFinished = false
@ -53,6 +61,7 @@ struct TimelineFeedView: View {
}
}
}
.navigationBarTitle(self.getTitle())
.overlay(alignment: .center) {
if firstLoadFinished == false {
LoadingIndicator()
@ -88,11 +97,7 @@ struct TimelineFeedView: View {
return
}
let statuses = try await PublicTimelineService.shared.getStatuses(
accountData: self.applicationState.accountData,
local: isLocalOnly,
remote: !isLocalOnly,
limit: self.defaultLimit)
let statuses = try await self.loadFromApi()
var inPlaceStatuses: [StatusViewModel] = []
for item in statuses {
@ -109,12 +114,7 @@ struct TimelineFeedView: View {
private func loadMoreStatuses() async throws {
if let lastStatusId = self.statusViewModels.last?.id {
let previousStatuses = try await PublicTimelineService.shared.getStatuses(
accountData: self.applicationState.accountData,
local: isLocalOnly,
remote: !isLocalOnly,
maxId: lastStatusId,
limit: self.defaultLimit)
let previousStatuses = try await self.loadFromApi(maxId: lastStatusId)
if previousStatuses.count < self.defaultLimit {
self.allItemsLoaded = true
@ -131,12 +131,7 @@ struct TimelineFeedView: View {
private func loadTopStatuses() async throws {
if let firstStatusId = self.statusViewModels.first?.id {
let newestStatuses = try await PublicTimelineService.shared.getStatuses(
accountData: self.applicationState.accountData,
local: isLocalOnly,
remote: !isLocalOnly,
sinceId: firstStatusId,
limit: self.defaultLimit)
let newestStatuses = try await self.loadFromApi(sinceId: firstStatusId)
var inPlaceStatuses: [StatusViewModel] = []
for item in newestStatuses {
@ -146,4 +141,54 @@ struct TimelineFeedView: View {
self.statusViewModels.insert(contentsOf: inPlaceStatuses, at: 0)
}
}
private func loadFromApi(maxId: String? = nil, sinceId: String? = nil, minId: String? = nil) async throws -> [Status] {
switch self.listType {
case .local:
return try await PublicTimelineService.shared.getStatuses(
accountData: self.applicationState.accountData,
local: true,
remote: false,
maxId: maxId,
sinceId: sinceId,
minId: minId,
limit: self.defaultLimit)
case .federated:
return try await PublicTimelineService.shared.getStatuses(
accountData: self.applicationState.accountData,
local: false,
remote: true,
maxId: maxId,
sinceId: sinceId,
minId: minId,
limit: self.defaultLimit)
case .favourites:
return try await AccountService.shared.favourites(
accountData: self.applicationState.accountData,
maxId: maxId,
sinceId: sinceId,
minId: minId,
limit: self.defaultLimit)
case .bookmarks:
return try await AccountService.shared.bookmarks(
accountData: self.applicationState.accountData,
maxId: maxId,
sinceId: sinceId,
minId: minId,
limit: self.defaultLimit)
}
}
private func getTitle() -> String {
switch self.listType {
case .local:
return "Local"
case .federated:
return "Federeted"
case .favourites:
return "Favourites"
case .bookmarks:
return "Bookmarks"
}
}
}

View File

@ -162,19 +162,15 @@ struct UserProfileHeader: View {
Divider()
}
Button {
Task {
// await onMuteAccount()
}
} label: {
NavigationLink(destination: StatusesView(accountId: applicationState.accountData?.id ?? String.empty(), listType: .favourites)
.environmentObject(applicationState)
) {
Label("Favourites", systemImage: "hand.thumbsup")
}
Button {
Task {
// await onMuteAccount()
}
} label: {
NavigationLink(destination: StatusesView(accountId: applicationState.accountData?.id ?? String.empty(), listType: .bookmarks)
.environmentObject(applicationState)
) {
Label("Bookmarks", systemImage: "bookmark")
}
}, label: {