feat: implement following list

This commit is contained in:
CMK 2021-11-02 16:12:20 +08:00
parent 07eab320f4
commit 30b2a35b84
6 changed files with 162 additions and 6 deletions

View File

@ -320,6 +320,7 @@
DB66728C25F9F8DC00D60309 /* ComposeViewModel+DataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB66728B25F9F8DC00D60309 /* ComposeViewModel+DataSource.swift */; }; DB66728C25F9F8DC00D60309 /* ComposeViewModel+DataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB66728B25F9F8DC00D60309 /* ComposeViewModel+DataSource.swift */; };
DB66729625F9F91600D60309 /* ComposeStatusSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB66729525F9F91600D60309 /* ComposeStatusSection.swift */; }; DB66729625F9F91600D60309 /* ComposeStatusSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB66729525F9F91600D60309 /* ComposeStatusSection.swift */; };
DB66729C25F9F91F00D60309 /* ComposeStatusItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB66729B25F9F91F00D60309 /* ComposeStatusItem.swift */; }; DB66729C25F9F91F00D60309 /* ComposeStatusItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB66729B25F9F91F00D60309 /* ComposeStatusItem.swift */; };
DB67D08427312970006A36CF /* APIService+Following.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB67D08327312970006A36CF /* APIService+Following.swift */; };
DB68045B2636DC6A00430867 /* MastodonNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB68045A2636DC6A00430867 /* MastodonNotification.swift */; }; DB68045B2636DC6A00430867 /* MastodonNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB68045A2636DC6A00430867 /* MastodonNotification.swift */; };
DB6804662636DC9000430867 /* String.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D939AB425EDD8A90076FA61 /* String.swift */; }; DB6804662636DC9000430867 /* String.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D939AB425EDD8A90076FA61 /* String.swift */; };
DB68046C2636DC9E00430867 /* MastodonNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB68045A2636DC6A00430867 /* MastodonNotification.swift */; }; DB68046C2636DC9E00430867 /* MastodonNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB68045A2636DC6A00430867 /* MastodonNotification.swift */; };
@ -1139,6 +1140,7 @@
DB66728B25F9F8DC00D60309 /* ComposeViewModel+DataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ComposeViewModel+DataSource.swift"; sourceTree = "<group>"; }; DB66728B25F9F8DC00D60309 /* ComposeViewModel+DataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ComposeViewModel+DataSource.swift"; sourceTree = "<group>"; };
DB66729525F9F91600D60309 /* ComposeStatusSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeStatusSection.swift; sourceTree = "<group>"; }; DB66729525F9F91600D60309 /* ComposeStatusSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeStatusSection.swift; sourceTree = "<group>"; };
DB66729B25F9F91F00D60309 /* ComposeStatusItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeStatusItem.swift; sourceTree = "<group>"; }; DB66729B25F9F91F00D60309 /* ComposeStatusItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeStatusItem.swift; sourceTree = "<group>"; };
DB67D08327312970006A36CF /* APIService+Following.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Following.swift"; sourceTree = "<group>"; };
DB68045A2636DC6A00430867 /* MastodonNotification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonNotification.swift; sourceTree = "<group>"; }; DB68045A2636DC6A00430867 /* MastodonNotification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonNotification.swift; sourceTree = "<group>"; };
DB68047F2637CD4C00430867 /* AppShared.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = AppShared.framework; sourceTree = BUILT_PRODUCTS_DIR; }; DB68047F2637CD4C00430867 /* AppShared.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = AppShared.framework; sourceTree = BUILT_PRODUCTS_DIR; };
DB6804812637CD4C00430867 /* AppShared.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppShared.h; sourceTree = "<group>"; }; DB6804812637CD4C00430867 /* AppShared.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppShared.h; sourceTree = "<group>"; };
@ -2326,6 +2328,7 @@
2D34D9DA261494120081BFC0 /* APIService+Search.swift */, 2D34D9DA261494120081BFC0 /* APIService+Search.swift */,
0F202212261351F5000C64BF /* APIService+HashtagTimeline.swift */, 0F202212261351F5000C64BF /* APIService+HashtagTimeline.swift */,
DB6B74F9272FC2B500C70B6E /* APIService+Follower.swift */, DB6B74F9272FC2B500C70B6E /* APIService+Follower.swift */,
DB67D08327312970006A36CF /* APIService+Following.swift */,
DBCC3B9426157E6E0045B23D /* APIService+Relationship.swift */, DBCC3B9426157E6E0045B23D /* APIService+Relationship.swift */,
5B24BBE1262DB19100A9381B /* APIService+Report.swift */, 5B24BBE1262DB19100A9381B /* APIService+Report.swift */,
DBAE3F932616E28B004B8251 /* APIService+Follow.swift */, DBAE3F932616E28B004B8251 /* APIService+Follow.swift */,
@ -4125,6 +4128,7 @@
2D82B9FF25E7863200E36F0F /* OnboardingViewControllerAppearance.swift in Sources */, 2D82B9FF25E7863200E36F0F /* OnboardingViewControllerAppearance.swift in Sources */,
5DF1054725F8870E00D6C0D4 /* VideoPlayerViewModel.swift in Sources */, 5DF1054725F8870E00D6C0D4 /* VideoPlayerViewModel.swift in Sources */,
DB73BF43271192BB00781945 /* InstanceService.swift in Sources */, DB73BF43271192BB00781945 /* InstanceService.swift in Sources */,
DB67D08427312970006A36CF /* APIService+Following.swift in Sources */,
DBA9443A265CC0FC00C537E1 /* Fields.swift in Sources */, DBA9443A265CC0FC00C537E1 /* Fields.swift in Sources */,
2DE0FAC12615F04D00CDF649 /* RecommendHashTagSection.swift in Sources */, 2DE0FAC12615F04D00CDF649 /* RecommendHashTagSection.swift in Sources */,
DBA5E7A5263BD28C004598BB /* ContextMenuImagePreviewViewModel.swift in Sources */, DBA5E7A5263BD28C004598BB /* ContextMenuImagePreviewViewModel.swift in Sources */,

View File

@ -7,12 +7,12 @@
<key>AppShared.xcscheme_^#shared#^_</key> <key>AppShared.xcscheme_^#shared#^_</key>
<dict> <dict>
<key>orderHint</key> <key>orderHint</key>
<integer>36</integer> <integer>37</integer>
</dict> </dict>
<key>CoreDataStack.xcscheme_^#shared#^_</key> <key>CoreDataStack.xcscheme_^#shared#^_</key>
<dict> <dict>
<key>orderHint</key> <key>orderHint</key>
<integer>35</integer> <integer>36</integer>
</dict> </dict>
<key>Mastodon - ASDK.xcscheme_^#shared#^_</key> <key>Mastodon - ASDK.xcscheme_^#shared#^_</key>
<dict> <dict>
@ -97,7 +97,7 @@
<key>MastodonIntent.xcscheme_^#shared#^_</key> <key>MastodonIntent.xcscheme_^#shared#^_</key>
<dict> <dict>
<key>orderHint</key> <key>orderHint</key>
<integer>38</integer> <integer>35</integer>
</dict> </dict>
<key>MastodonIntents.xcscheme_^#shared#^_</key> <key>MastodonIntents.xcscheme_^#shared#^_</key>
<dict> <dict>
@ -117,7 +117,7 @@
<key>ShareActionExtension.xcscheme_^#shared#^_</key> <key>ShareActionExtension.xcscheme_^#shared#^_</key>
<dict> <dict>
<key>orderHint</key> <key>orderHint</key>
<integer>37</integer> <integer>38</integer>
</dict> </dict>
</dict> </dict>
<key>SuppressBuildableAutocreation</key> <key>SuppressBuildableAutocreation</key>

View File

@ -155,7 +155,7 @@ extension FollowerListViewModel.State {
let maxID = response.link?.maxID let maxID = response.link?.maxID
if maxID != nil { if hasNewAppend && maxID != nil {
stateMachine.enter(Idle.self) stateMachine.enter(Idle.self)
} else { } else {
stateMachine.enter(NoMore.self) stateMachine.enter(NoMore.self)

View File

@ -128,7 +128,7 @@ extension FollowingListViewModel.State {
return return
} }
viewModel.context.apiService.followers( viewModel.context.apiService.following(
userID: userID, userID: userID,
maxID: maxID, maxID: maxID,
authorizationBox: activeMastodonAuthenticationBox authorizationBox: activeMastodonAuthenticationBox

View File

@ -0,0 +1,70 @@
//
// APIService+Following.swift
// Mastodon
//
// Created by Cirno MainasuK on 2021-11-2.
//
import UIKit
import Combine
import CoreData
import CoreDataStack
import CommonOSLog
import MastodonSDK
extension APIService {
func following(
userID: Mastodon.Entity.Account.ID,
maxID: String?,
authorizationBox: MastodonAuthenticationBox
) -> AnyPublisher<Mastodon.Response.Content<[Mastodon.Entity.Account]>, Error> {
let domain = authorizationBox.domain
let authorization = authorizationBox.userAuthorization
let requestMastodonUserID = authorizationBox.userID
let query = Mastodon.API.Account.FollowingQuery(
maxID: maxID,
limit: nil
)
return Mastodon.API.Account.following(
session: session,
domain: domain,
userID: userID,
query: query,
authorization: authorization
)
.flatMap { response -> AnyPublisher<Mastodon.Response.Content<[Mastodon.Entity.Account]>, Error> in
let managedObjectContext = self.backgroundManagedObjectContext
return managedObjectContext.performChanges {
let requestMastodonUserRequest = MastodonUser.sortedFetchRequest
requestMastodonUserRequest.predicate = MastodonUser.predicate(domain: domain, id: requestMastodonUserID)
requestMastodonUserRequest.fetchLimit = 1
guard let requestMastodonUser = managedObjectContext.safeFetch(requestMastodonUserRequest).first else { return }
for entity in response.value {
_ = APIService.CoreData.createOrMergeMastodonUser(
into: managedObjectContext,
for: requestMastodonUser,
in: domain,
entity: entity,
userCache: nil,
networkDate: response.networkDate,
log: .api
)
}
}
.tryMap { result -> Mastodon.Response.Content<[Mastodon.Entity.Account]> in
switch result {
case .success:
return response
case .failure(let error):
throw error
}
}
.eraseToAnyPublisher()
}
.eraseToAnyPublisher()
}
}

View File

@ -0,0 +1,82 @@
//
// Mastodon+API+Account+Following.swift
//
//
// Created by Cirno MainasuK on 2021-11-2.
//
import Foundation
import Combine
extension Mastodon.API.Account {
static func followingEndpointURL(domain: String, userID: Mastodon.Entity.Account.ID) -> URL {
return Mastodon.API.endpointURL(domain: domain)
.appendingPathComponent("accounts")
.appendingPathComponent(userID)
.appendingPathComponent("following")
}
/// Following
///
/// Accounts which the given account is following, if network is not hidden by the account owner.
///
/// - Since: 0.0.0
/// - Version: 3.4.1
/// # Reference
/// [Document](https://docs.joinmastodon.org/methods/accounts/)
/// - Parameters:
/// - session: `URLSession`
/// - domain: Mastodon instance domain. e.g. "example.com"
/// - userID: ID of the account in the database
/// - authorization: User token
/// - Returns: `AnyPublisher` contains `[Account]` nested in the response
public static func following(
session: URLSession,
domain: String,
userID: Mastodon.Entity.Account.ID,
query: FollowingQuery,
authorization: Mastodon.API.OAuth.Authorization
) -> AnyPublisher<Mastodon.Response.Content<[Mastodon.Entity.Account]>, Error> {
let request = Mastodon.API.get(
url: followingEndpointURL(domain: domain, userID: userID),
query: query,
authorization: authorization
)
return session.dataTaskPublisher(for: request)
.tryMap { data, response in
let value = try Mastodon.API.decode(type: [Mastodon.Entity.Account].self, from: data, response: response)
return Mastodon.Response.Content(value: value, response: response)
}
.eraseToAnyPublisher()
}
public struct FollowingQuery: Codable, GetQuery {
public let maxID: String?
public let limit: Int? // default 40
enum CodingKeys: String, CodingKey {
case maxID = "max_id"
case limit
}
public init(
maxID: String?,
limit: Int?
) {
self.maxID = maxID
self.limit = limit
}
var queryItems: [URLQueryItem]? {
var items: [URLQueryItem] = []
maxID.flatMap { items.append(URLQueryItem(name: "max_id", value: $0)) }
limit.flatMap { items.append(URLQueryItem(name: "limit", value: String($0))) }
guard !items.isEmpty else { return nil }
return items
}
}
}