mirror of
https://github.com/metabolist/metatext.git
synced 2024-11-22 08:10:59 +00:00
Account statuses wip
This commit is contained in:
parent
f2344fcbe9
commit
21f09b13e6
15 changed files with 336 additions and 72 deletions
12
DB/Sources/DB/Content/AccountPinnedStatusJoin.swift
Normal file
12
DB/Sources/DB/Content/AccountPinnedStatusJoin.swift
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
// Copyright © 2020 Metabolist. All rights reserved.
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import GRDB
|
||||||
|
|
||||||
|
struct AccountPinnedStatusJoin: Codable, FetchableRecord, PersistableRecord {
|
||||||
|
let accountId: String
|
||||||
|
let statusId: String
|
||||||
|
let index: Int
|
||||||
|
|
||||||
|
static let status = belongsTo(StatusRecord.self, using: ForeignKey([Column("statusId")]))
|
||||||
|
}
|
|
@ -39,6 +39,18 @@ extension AccountRecord: FetchableRecord, PersistableRecord {
|
||||||
|
|
||||||
extension AccountRecord {
|
extension AccountRecord {
|
||||||
static let moved = belongsTo(AccountRecord.self, key: "moved")
|
static let moved = belongsTo(AccountRecord.self, key: "moved")
|
||||||
|
static let pinnedStatusJoins = hasMany(
|
||||||
|
AccountPinnedStatusJoin.self,
|
||||||
|
using: ForeignKey([Column("accountId")]))
|
||||||
|
.order(Column("index"))
|
||||||
|
static let pinnedStatuses = hasMany(
|
||||||
|
StatusRecord.self,
|
||||||
|
through: pinnedStatusJoins,
|
||||||
|
using: AccountPinnedStatusJoin.status)
|
||||||
|
|
||||||
|
var pinnedStatuses: QueryInterfaceRequest<StatusResult> {
|
||||||
|
request(for: Self.pinnedStatuses).statusResultRequest
|
||||||
|
}
|
||||||
|
|
||||||
init(account: Account) {
|
init(account: Account) {
|
||||||
id = account.id
|
id = account.id
|
||||||
|
|
12
DB/Sources/DB/Content/AccountStatusJoin.swift
Normal file
12
DB/Sources/DB/Content/AccountStatusJoin.swift
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
// Copyright © 2020 Metabolist. All rights reserved.
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import GRDB
|
||||||
|
|
||||||
|
struct AccountStatusJoin: Codable, FetchableRecord, PersistableRecord {
|
||||||
|
let accountId: String
|
||||||
|
let statusId: String
|
||||||
|
let collection: AccountStatusCollection
|
||||||
|
|
||||||
|
static let status = belongsTo(StatusRecord.self, using: ForeignKey([Column("statusId")]))
|
||||||
|
}
|
|
@ -82,6 +82,38 @@ public extension ContentDatabase {
|
||||||
.eraseToAnyPublisher()
|
.eraseToAnyPublisher()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func insert(pinnedStatuses: [Status], accountID: String) -> AnyPublisher<Never, Error> {
|
||||||
|
databaseQueue.writePublisher {
|
||||||
|
for (index, status) in pinnedStatuses.enumerated() {
|
||||||
|
try status.save($0)
|
||||||
|
|
||||||
|
try AccountPinnedStatusJoin(accountId: accountID, statusId: status.id, index: index).save($0)
|
||||||
|
}
|
||||||
|
|
||||||
|
try AccountPinnedStatusJoin.filter(
|
||||||
|
Column("accountId") == accountID
|
||||||
|
&& !pinnedStatuses.map(\.id).contains(Column("statusId")))
|
||||||
|
.deleteAll($0)
|
||||||
|
}
|
||||||
|
.ignoreOutput()
|
||||||
|
.eraseToAnyPublisher()
|
||||||
|
}
|
||||||
|
|
||||||
|
func insert(
|
||||||
|
statuses: [Status],
|
||||||
|
accountID: String,
|
||||||
|
collection: AccountStatusCollection) -> AnyPublisher<Never, Error> {
|
||||||
|
databaseQueue.writePublisher {
|
||||||
|
for status in statuses {
|
||||||
|
try status.save($0)
|
||||||
|
|
||||||
|
try AccountStatusJoin(accountId: accountID, statusId: status.id, collection: collection).save($0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.ignoreOutput()
|
||||||
|
.eraseToAnyPublisher()
|
||||||
|
}
|
||||||
|
|
||||||
func setLists(_ lists: [MastodonList]) -> AnyPublisher<Never, Error> {
|
func setLists(_ lists: [MastodonList]) -> AnyPublisher<Never, Error> {
|
||||||
databaseQueue.writePublisher {
|
databaseQueue.writePublisher {
|
||||||
for list in lists {
|
for list in lists {
|
||||||
|
@ -158,6 +190,35 @@ public extension ContentDatabase {
|
||||||
.eraseToAnyPublisher()
|
.eraseToAnyPublisher()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func statusesObservation(
|
||||||
|
accountID: String,
|
||||||
|
collection: AccountStatusCollection) -> AnyPublisher<[[Status]], Error> {
|
||||||
|
ValueObservation.tracking { db -> [[StatusResult]] in
|
||||||
|
let statuses = try StatusRecord.filter(
|
||||||
|
AccountStatusJoin
|
||||||
|
.select(Column("statusId"), as: String.self)
|
||||||
|
.filter(sql: "accountId = ? AND collection = ?", arguments: [accountID, collection.rawValue])
|
||||||
|
.contains(Column("id")))
|
||||||
|
.order(Column("createdAt").desc)
|
||||||
|
.statusResultRequest
|
||||||
|
.fetchAll(db)
|
||||||
|
|
||||||
|
if
|
||||||
|
case .statuses = collection,
|
||||||
|
let accountRecord = try AccountRecord.filter(Column("id") == accountID).fetchOne(db) {
|
||||||
|
let pinnedStatuses = try accountRecord.pinnedStatuses.fetchAll(db)
|
||||||
|
|
||||||
|
return [pinnedStatuses, statuses]
|
||||||
|
} else {
|
||||||
|
return [statuses]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.removeDuplicates()
|
||||||
|
.publisher(in: databaseQueue)
|
||||||
|
.map { $0.map { $0.map(Status.init(result:)) } }
|
||||||
|
.eraseToAnyPublisher()
|
||||||
|
}
|
||||||
|
|
||||||
func listsObservation() -> AnyPublisher<[Timeline], Error> {
|
func listsObservation() -> AnyPublisher<[Timeline], Error> {
|
||||||
ValueObservation.tracking(Timeline.filter(Column("listTitle") != nil)
|
ValueObservation.tracking(Timeline.filter(Column("listTitle") != nil)
|
||||||
.order(Column("listTitle").collating(.localizedCaseInsensitiveCompare).asc)
|
.order(Column("listTitle").collating(.localizedCaseInsensitiveCompare).asc)
|
||||||
|
@ -287,17 +348,29 @@ private extension ContentDatabase {
|
||||||
private static func createTemporaryTables(_ writer: DatabaseWriter) throws {
|
private static func createTemporaryTables(_ writer: DatabaseWriter) throws {
|
||||||
try writer.write { db in
|
try writer.write { db in
|
||||||
try db.create(table: "statusContextJoin", temporary: true) { t in
|
try db.create(table: "statusContextJoin", temporary: true) { t in
|
||||||
t.column("parentId", .text)
|
t.column("parentId", .text).indexed().notNull()
|
||||||
.indexed()
|
t.column("statusId", .text).indexed().notNull()
|
||||||
.notNull()
|
|
||||||
t.column("statusId", .text)
|
|
||||||
.indexed()
|
|
||||||
.notNull()
|
|
||||||
t.column("section", .text).notNull()
|
t.column("section", .text).notNull()
|
||||||
t.column("index", .integer).notNull()
|
t.column("index", .integer).notNull()
|
||||||
|
|
||||||
t.primaryKey(["parentId", "statusId"], onConflict: .replace)
|
t.primaryKey(["parentId", "statusId"], onConflict: .replace)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try db.create(table: "accountPinnedStatusJoin", temporary: true) { t in
|
||||||
|
t.column("accountId", .text).indexed().notNull()
|
||||||
|
t.column("statusId", .text).indexed().notNull()
|
||||||
|
t.column("index", .integer).notNull()
|
||||||
|
|
||||||
|
t.primaryKey(["accountId", "statusId"], onConflict: .replace)
|
||||||
|
}
|
||||||
|
|
||||||
|
try db.create(table: "accountStatusJoin", temporary: true) { t in
|
||||||
|
t.column("accountId", .text).indexed().notNull()
|
||||||
|
t.column("statusId", .text).indexed().notNull()
|
||||||
|
t.column("collection", .text).notNull()
|
||||||
|
|
||||||
|
t.primaryKey(["accountId", "statusId", "collection"], onConflict: .replace)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
9
DB/Sources/DB/Entities/AccountStatusCollection.swift
Normal file
9
DB/Sources/DB/Entities/AccountStatusCollection.swift
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
// Copyright © 2020 Metabolist. All rights reserved.
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
public enum AccountStatusCollection: String, Codable {
|
||||||
|
case statuses
|
||||||
|
case statusesAndReplies
|
||||||
|
case media
|
||||||
|
}
|
|
@ -0,0 +1,54 @@
|
||||||
|
// Copyright © 2020 Metabolist. All rights reserved.
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import HTTP
|
||||||
|
import Mastodon
|
||||||
|
|
||||||
|
public enum StatusesEndpoint {
|
||||||
|
case timelinesPublic(local: Bool)
|
||||||
|
case timelinesTag(String)
|
||||||
|
case timelinesHome
|
||||||
|
case timelinesList(id: String)
|
||||||
|
case accountsStatuses(id: String, excludeReplies: Bool, onlyMedia: Bool, pinned: Bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
extension StatusesEndpoint: Endpoint {
|
||||||
|
public typealias ResultType = [Status]
|
||||||
|
|
||||||
|
public var context: [String] {
|
||||||
|
switch self {
|
||||||
|
case .timelinesPublic, .timelinesTag, .timelinesHome, .timelinesList:
|
||||||
|
return defaultContext + ["timelines"]
|
||||||
|
case .accountsStatuses:
|
||||||
|
return defaultContext + ["accounts"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public var pathComponentsInContext: [String] {
|
||||||
|
switch self {
|
||||||
|
case .timelinesPublic:
|
||||||
|
return ["public"]
|
||||||
|
case let .timelinesTag(tag):
|
||||||
|
return ["tag", tag]
|
||||||
|
case .timelinesHome:
|
||||||
|
return ["home"]
|
||||||
|
case let .timelinesList(id):
|
||||||
|
return ["list", id]
|
||||||
|
case let .accountsStatuses(id, _, _, _):
|
||||||
|
return [id, "statuses"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public var parameters: [String: Any]? {
|
||||||
|
switch self {
|
||||||
|
case let .timelinesPublic(local):
|
||||||
|
return ["local": local]
|
||||||
|
case let .accountsStatuses(_, excludeReplies, onlyMedia, pinned):
|
||||||
|
return ["exclude_replies": excludeReplies, "only_media": onlyMedia, "pinned": pinned]
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public var method: HTTPMethod { .get }
|
||||||
|
}
|
|
@ -1,44 +0,0 @@
|
||||||
// Copyright © 2020 Metabolist. All rights reserved.
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
import HTTP
|
|
||||||
import Mastodon
|
|
||||||
|
|
||||||
public enum TimelinesEndpoint {
|
|
||||||
case `public`(local: Bool)
|
|
||||||
case tag(String)
|
|
||||||
case home
|
|
||||||
case list(id: String)
|
|
||||||
}
|
|
||||||
|
|
||||||
extension TimelinesEndpoint: Endpoint {
|
|
||||||
public typealias ResultType = [Status]
|
|
||||||
|
|
||||||
public var context: [String] {
|
|
||||||
defaultContext + ["timelines"]
|
|
||||||
}
|
|
||||||
|
|
||||||
public var pathComponentsInContext: [String] {
|
|
||||||
switch self {
|
|
||||||
case .public:
|
|
||||||
return ["public"]
|
|
||||||
case let .tag(tag):
|
|
||||||
return ["tag", tag]
|
|
||||||
case .home:
|
|
||||||
return ["home"]
|
|
||||||
case let .list(id):
|
|
||||||
return ["list", id]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public var parameters: [String: Any]? {
|
|
||||||
switch self {
|
|
||||||
case let .public(local):
|
|
||||||
return ["local": local]
|
|
||||||
default:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public var method: HTTPMethod { .get }
|
|
||||||
}
|
|
|
@ -4,18 +4,18 @@ import Foundation
|
||||||
import Mastodon
|
import Mastodon
|
||||||
|
|
||||||
public extension Timeline {
|
public extension Timeline {
|
||||||
var endpoint: TimelinesEndpoint {
|
var endpoint: StatusesEndpoint {
|
||||||
switch self {
|
switch self {
|
||||||
case .home:
|
case .home:
|
||||||
return .home
|
return .timelinesHome
|
||||||
case .local:
|
case .local:
|
||||||
return .public(local: true)
|
return .timelinesPublic(local: true)
|
||||||
case .federated:
|
case .federated:
|
||||||
return .public(local: false)
|
return .timelinesPublic(local: false)
|
||||||
case let .list(list):
|
case let .list(list):
|
||||||
return .list(id: list.id)
|
return .timelinesList(id: list.id)
|
||||||
case let .tag(tag):
|
case let .tag(tag):
|
||||||
return .tag(tag)
|
return .timelinesTag(tag)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ import Foundation
|
||||||
import MastodonAPI
|
import MastodonAPI
|
||||||
import Stubbing
|
import Stubbing
|
||||||
|
|
||||||
extension TimelinesEndpoint: Stubbing {
|
extension StatusesEndpoint: Stubbing {
|
||||||
public func data(url: URL) -> Data? {
|
public func data(url: URL) -> Data? {
|
||||||
StubData.timeline
|
StubData.timeline
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
// Copyright © 2020 Metabolist. All rights reserved.
|
||||||
|
|
||||||
|
import DB
|
||||||
|
|
||||||
|
public typealias AccountStatusCollection = DB.AccountStatusCollection
|
|
@ -0,0 +1,40 @@
|
||||||
|
// Copyright © 2020 Metabolist. All rights reserved.
|
||||||
|
|
||||||
|
import Combine
|
||||||
|
import DB
|
||||||
|
import Foundation
|
||||||
|
import Mastodon
|
||||||
|
import MastodonAPI
|
||||||
|
|
||||||
|
public struct AccountStatusesService {
|
||||||
|
private let accountID: String
|
||||||
|
private let mastodonAPIClient: MastodonAPIClient
|
||||||
|
private let contentDatabase: ContentDatabase
|
||||||
|
|
||||||
|
init(id: String, mastodonAPIClient: MastodonAPIClient, contentDatabase: ContentDatabase) {
|
||||||
|
accountID = id
|
||||||
|
self.mastodonAPIClient = mastodonAPIClient
|
||||||
|
self.contentDatabase = contentDatabase
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public extension AccountStatusesService {
|
||||||
|
func statusListService(collectionPublisher: AnyPublisher<AccountStatusCollection, Never>) -> StatusListService {
|
||||||
|
StatusListService(
|
||||||
|
accountID: accountID,
|
||||||
|
collection: collectionPublisher,
|
||||||
|
mastodonAPIClient: mastodonAPIClient,
|
||||||
|
contentDatabase: contentDatabase)
|
||||||
|
}
|
||||||
|
|
||||||
|
func fetchPinnedStatuses() -> AnyPublisher<Never, Error> {
|
||||||
|
mastodonAPIClient.request(
|
||||||
|
StatusesEndpoint.accountsStatuses(
|
||||||
|
id: accountID,
|
||||||
|
excludeReplies: true,
|
||||||
|
onlyMedia: false,
|
||||||
|
pinned: true))
|
||||||
|
.flatMap { contentDatabase.insert(pinnedStatuses: $0, accountID: accountID) }
|
||||||
|
.eraseToAnyPublisher()
|
||||||
|
}
|
||||||
|
}
|
|
@ -53,7 +53,7 @@ public extension InstanceURLService {
|
||||||
httpClient.request(
|
httpClient.request(
|
||||||
MastodonAPITarget(
|
MastodonAPITarget(
|
||||||
baseURL: url,
|
baseURL: url,
|
||||||
endpoint: TimelinesEndpoint.public(local: true),
|
endpoint: StatusesEndpoint.timelinesPublic(local: true),
|
||||||
accessToken: nil))
|
accessToken: nil))
|
||||||
.map { _ in true }
|
.map { _ in true }
|
||||||
.eraseToAnyPublisher()
|
.eraseToAnyPublisher()
|
||||||
|
|
|
@ -47,6 +47,51 @@ extension StatusListService {
|
||||||
.eraseToAnyPublisher()
|
.eraseToAnyPublisher()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
init(
|
||||||
|
accountID: String,
|
||||||
|
collection: AnyPublisher<AccountStatusCollection, Never>,
|
||||||
|
mastodonAPIClient: MastodonAPIClient,
|
||||||
|
contentDatabase: ContentDatabase) {
|
||||||
|
self.init(
|
||||||
|
statusSections: collection
|
||||||
|
.flatMap { contentDatabase.statusesObservation(accountID: accountID, collection: $0) }
|
||||||
|
.eraseToAnyPublisher(),
|
||||||
|
paginates: true,
|
||||||
|
contextParentID: nil,
|
||||||
|
title: "turn this into a closure or publisher",
|
||||||
|
filterContext: .account,
|
||||||
|
mastodonAPIClient: mastodonAPIClient,
|
||||||
|
contentDatabase: contentDatabase) { maxID, minID in
|
||||||
|
Just((maxID, minID)).combineLatest(collection).flatMap { params -> AnyPublisher<Never, Error> in
|
||||||
|
let ((maxID, minID), collection) = params
|
||||||
|
let excludeReplies: Bool
|
||||||
|
let onlyMedia: Bool
|
||||||
|
|
||||||
|
switch collection {
|
||||||
|
case .statuses:
|
||||||
|
excludeReplies = true
|
||||||
|
onlyMedia = false
|
||||||
|
case .statusesAndReplies:
|
||||||
|
excludeReplies = false
|
||||||
|
onlyMedia = false
|
||||||
|
case .media:
|
||||||
|
excludeReplies = true
|
||||||
|
onlyMedia = true
|
||||||
|
}
|
||||||
|
|
||||||
|
let endpoint = StatusesEndpoint.accountsStatuses(
|
||||||
|
id: accountID,
|
||||||
|
excludeReplies: excludeReplies,
|
||||||
|
onlyMedia: onlyMedia,
|
||||||
|
pinned: false)
|
||||||
|
return mastodonAPIClient.request(Paged(endpoint, maxID: maxID, minID: minID))
|
||||||
|
.flatMap { contentDatabase.insert(statuses: $0, accountID: accountID, collection: collection) }
|
||||||
|
.eraseToAnyPublisher()
|
||||||
|
}
|
||||||
|
.eraseToAnyPublisher()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public extension StatusListService {
|
public extension StatusListService {
|
||||||
|
@ -66,6 +111,10 @@ public extension StatusListService {
|
||||||
Self(timeline: timeline, mastodonAPIClient: mastodonAPIClient, contentDatabase: contentDatabase)
|
Self(timeline: timeline, mastodonAPIClient: mastodonAPIClient, contentDatabase: contentDatabase)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func service(accountID: String) -> AccountStatusesService {
|
||||||
|
AccountStatusesService(id: accountID, mastodonAPIClient: mastodonAPIClient, contentDatabase: contentDatabase)
|
||||||
|
}
|
||||||
|
|
||||||
func contextService(statusID: String) -> Self {
|
func contextService(statusID: String) -> Self {
|
||||||
Self(statusSections: contentDatabase.contextObservation(parentID: statusID),
|
Self(statusSections: contentDatabase.contextObservation(parentID: statusID),
|
||||||
paginates: false,
|
paginates: false,
|
||||||
|
|
39
ViewModels/Sources/ViewModels/AccountStatusesViewModel.swift
Normal file
39
ViewModels/Sources/ViewModels/AccountStatusesViewModel.swift
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
// Copyright © 2020 Metabolist. All rights reserved.
|
||||||
|
|
||||||
|
import Combine
|
||||||
|
import Foundation
|
||||||
|
import Mastodon
|
||||||
|
import ServiceLayer
|
||||||
|
|
||||||
|
public class AccountStatusesViewModel: StatusListViewModel {
|
||||||
|
@Published var collection: AccountStatusCollection
|
||||||
|
private let accountStatusesService: AccountStatusesService
|
||||||
|
private var cancellables = Set<AnyCancellable>()
|
||||||
|
|
||||||
|
init(accountStatusesService: AccountStatusesService) {
|
||||||
|
self.accountStatusesService = accountStatusesService
|
||||||
|
|
||||||
|
var collection = Published(initialValue: AccountStatusCollection.statuses)
|
||||||
|
|
||||||
|
_collection = collection
|
||||||
|
|
||||||
|
super.init(
|
||||||
|
statusListService: accountStatusesService.statusListService(
|
||||||
|
collectionPublisher: collection.projectedValue.eraseToAnyPublisher()))
|
||||||
|
}
|
||||||
|
|
||||||
|
public override func request(maxID: String? = nil, minID: String? = nil) {
|
||||||
|
if case .statuses = collection, maxID == nil {
|
||||||
|
accountStatusesService.fetchPinnedStatuses()
|
||||||
|
.assignErrorsToAlertItem(to: \.alertItem, on: self)
|
||||||
|
.sink { _ in }
|
||||||
|
.store(in: &cancellables)
|
||||||
|
}
|
||||||
|
|
||||||
|
super.request(maxID: maxID, minID: minID)
|
||||||
|
}
|
||||||
|
|
||||||
|
override func isPinned(status: Status) -> Bool {
|
||||||
|
collection == .statuses && statusIDs.first?.contains(status.id) ?? false
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,7 +5,7 @@ import Foundation
|
||||||
import Mastodon
|
import Mastodon
|
||||||
import ServiceLayer
|
import ServiceLayer
|
||||||
|
|
||||||
public final class StatusListViewModel: ObservableObject {
|
public class StatusListViewModel: ObservableObject {
|
||||||
@Published public private(set) var statusIDs = [[String]]()
|
@Published public private(set) var statusIDs = [[String]]()
|
||||||
@Published public var alertItem: AlertItem?
|
@Published public var alertItem: AlertItem?
|
||||||
@Published public private(set) var loading = false
|
@Published public private(set) var loading = false
|
||||||
|
@ -35,6 +35,19 @@ public final class StatusListViewModel: ObservableObject {
|
||||||
.map { $0.map { $0.map(\.id) } }
|
.map { $0.map { $0.map(\.id) } }
|
||||||
.assign(to: &$statusIDs)
|
.assign(to: &$statusIDs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func request(maxID: String? = nil, minID: String? = nil) {
|
||||||
|
statusListService.request(maxID: maxID, minID: minID)
|
||||||
|
.receive(on: DispatchQueue.main)
|
||||||
|
.assignErrorsToAlertItem(to: \.alertItem, on: self)
|
||||||
|
.handleEvents(
|
||||||
|
receiveSubscription: { [weak self] _ in self?.loading = true },
|
||||||
|
receiveCompletion: { [weak self] _ in self?.loading = false })
|
||||||
|
.sink { _ in }
|
||||||
|
.store(in: &cancellables)
|
||||||
|
}
|
||||||
|
|
||||||
|
func isPinned(status: Status) -> Bool { false }
|
||||||
}
|
}
|
||||||
|
|
||||||
public extension StatusListViewModel {
|
public extension StatusListViewModel {
|
||||||
|
@ -52,17 +65,6 @@ public extension StatusListViewModel {
|
||||||
|
|
||||||
var contextParentID: String? { statusListService.contextParentID }
|
var contextParentID: String? { statusListService.contextParentID }
|
||||||
|
|
||||||
func request(maxID: String? = nil, minID: String? = nil) {
|
|
||||||
statusListService.request(maxID: maxID, minID: minID)
|
|
||||||
.receive(on: DispatchQueue.main)
|
|
||||||
.assignErrorsToAlertItem(to: \.alertItem, on: self)
|
|
||||||
.handleEvents(
|
|
||||||
receiveSubscription: { [weak self] _ in self?.loading = true },
|
|
||||||
receiveCompletion: { [weak self] _ in self?.loading = false })
|
|
||||||
.sink { _ in }
|
|
||||||
.store(in: &cancellables)
|
|
||||||
}
|
|
||||||
|
|
||||||
func statusViewModel(id: String) -> StatusViewModel? {
|
func statusViewModel(id: String) -> StatusViewModel? {
|
||||||
guard let status = statuses[id] else { return nil }
|
guard let status = statuses[id] else { return nil }
|
||||||
|
|
||||||
|
@ -85,7 +87,7 @@ public extension StatusListViewModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
statusViewModel.isContextParent = status.id == statusListService.contextParentID
|
statusViewModel.isContextParent = status.id == statusListService.contextParentID
|
||||||
statusViewModel.isPinned = status.displayStatus.pinned ?? false
|
statusViewModel.isPinned = isPinned(status: status)
|
||||||
statusViewModel.isReplyInContext = isReplyInContext(status: status)
|
statusViewModel.isReplyInContext = isReplyInContext(status: status)
|
||||||
statusViewModel.hasReplyFollowing = hasReplyFollowing(status: status)
|
statusViewModel.hasReplyFollowing = hasReplyFollowing(status: status)
|
||||||
|
|
||||||
|
@ -117,7 +119,8 @@ private extension StatusListViewModel {
|
||||||
case let .url(url):
|
case let .url(url):
|
||||||
return .urlNavigation(url)
|
return .urlNavigation(url)
|
||||||
case let .accountID(id):
|
case let .accountID(id):
|
||||||
return nil
|
return .statusListNavigation(
|
||||||
|
AccountStatusesViewModel(accountStatusesService: statusListService.service(accountID: id)))
|
||||||
case let .statusID(id):
|
case let .statusID(id):
|
||||||
return .statusListNavigation(
|
return .statusListNavigation(
|
||||||
StatusListViewModel(
|
StatusListViewModel(
|
||||||
|
|
Loading…
Reference in a new issue