wip
This commit is contained in:
parent
6bbfb2d06e
commit
b32a85aebc
|
@ -544,7 +544,7 @@ public extension ContentDatabase {
|
||||||
accountIds.firstIndex(of: $0.record.id) ?? 0
|
accountIds.firstIndex(of: $0.record.id) ?? 0
|
||||||
< accountIds.firstIndex(of: $1.record.id) ?? 0
|
< accountIds.firstIndex(of: $1.record.id) ?? 0
|
||||||
}
|
}
|
||||||
.map { CollectionItem.account(.init(info: $0), .withoutNote) }
|
.map { CollectionItem.account(.init(info: $0), .withoutNote, nil) } // TODO: revisit
|
||||||
|
|
||||||
if let limit = limit, accounts.count >= limit {
|
if let limit = limit, accounts.count >= limit {
|
||||||
accounts.append(.moreResults(.init(scope: .accounts)))
|
accounts.append(.moreResults(.init(scope: .accounts)))
|
||||||
|
@ -568,7 +568,8 @@ public extension ContentDatabase {
|
||||||
CollectionItem.status(
|
CollectionItem.status(
|
||||||
.init(info: $0),
|
.init(info: $0),
|
||||||
.init(showContentToggled: $0.showContentToggled,
|
.init(showContentToggled: $0.showContentToggled,
|
||||||
showAttachmentsToggled: $0.showAttachmentsToggled))
|
showAttachmentsToggled: $0.showAttachmentsToggled),
|
||||||
|
$0.reblogRelationship ?? $0.relationship)
|
||||||
}
|
}
|
||||||
|
|
||||||
if let limit = limit, statuses.count >= limit {
|
if let limit = limit, statuses.count >= limit {
|
||||||
|
|
|
@ -49,7 +49,8 @@ extension ContextItemsInfo {
|
||||||
showAttachmentsToggled: statusInfo.showAttachmentsToggled,
|
showAttachmentsToggled: statusInfo.showAttachmentsToggled,
|
||||||
isContextParent: isContextParent,
|
isContextParent: isContextParent,
|
||||||
isReplyInContext: isReplyInContext,
|
isReplyInContext: isReplyInContext,
|
||||||
hasReplyFollowing: hasReplyFollowing))
|
hasReplyFollowing: hasReplyFollowing),
|
||||||
|
statusInfo.reblogRelationship ?? statusInfo.relationship)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.map { CollectionSection(items: $0) }
|
.map { CollectionSection(items: $0) }
|
||||||
|
|
|
@ -2,12 +2,15 @@
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import GRDB
|
import GRDB
|
||||||
|
import Mastodon
|
||||||
|
|
||||||
struct StatusInfo: Codable, Hashable, FetchableRecord {
|
struct StatusInfo: Codable, Hashable, FetchableRecord {
|
||||||
let record: StatusRecord
|
let record: StatusRecord
|
||||||
let accountInfo: AccountInfo
|
let accountInfo: AccountInfo
|
||||||
|
let relationship: Relationship?
|
||||||
let reblogAccountInfo: AccountInfo?
|
let reblogAccountInfo: AccountInfo?
|
||||||
let reblogRecord: StatusRecord?
|
let reblogRecord: StatusRecord?
|
||||||
|
let reblogRelationship: Relationship?
|
||||||
let showContentToggle: StatusShowContentToggle?
|
let showContentToggle: StatusShowContentToggle?
|
||||||
let reblogShowContentToggle: StatusShowContentToggle?
|
let reblogShowContentToggle: StatusShowContentToggle?
|
||||||
let showAttachmentsToggle: StatusShowAttachmentsToggle?
|
let showAttachmentsToggle: StatusShowAttachmentsToggle?
|
||||||
|
@ -50,7 +53,9 @@ private extension StatusInfo {
|
||||||
static func addingOptionalIncludes<T: DerivableRequest>(_ request: T) -> T where T.RowDecoder == StatusRecord {
|
static func addingOptionalIncludes<T: DerivableRequest>(_ request: T) -> T where T.RowDecoder == StatusRecord {
|
||||||
request.including(optional: AccountInfo.addingIncludes(StatusRecord.reblogAccount)
|
request.including(optional: AccountInfo.addingIncludes(StatusRecord.reblogAccount)
|
||||||
.forKey(CodingKeys.reblogAccountInfo))
|
.forKey(CodingKeys.reblogAccountInfo))
|
||||||
|
.including(optional: StatusRecord.relationship.forKey(CodingKeys.relationship))
|
||||||
.including(optional: StatusRecord.reblog.forKey(CodingKeys.reblogRecord))
|
.including(optional: StatusRecord.reblog.forKey(CodingKeys.reblogRecord))
|
||||||
|
.including(optional: StatusRecord.reblogRelationship.forKey(CodingKeys.reblogRelationship))
|
||||||
.including(optional: StatusRecord.showContentToggle.forKey(CodingKeys.showContentToggle))
|
.including(optional: StatusRecord.showContentToggle.forKey(CodingKeys.showContentToggle))
|
||||||
.including(optional: StatusRecord.reblogShowContentToggle.forKey(CodingKeys.reblogShowContentToggle))
|
.including(optional: StatusRecord.reblogShowContentToggle.forKey(CodingKeys.reblogShowContentToggle))
|
||||||
.including(optional: StatusRecord.showAttachmentsToggle.forKey(CodingKeys.showAttachmentsToggle))
|
.including(optional: StatusRecord.showAttachmentsToggle.forKey(CodingKeys.showAttachmentsToggle))
|
||||||
|
|
|
@ -72,6 +72,9 @@ extension StatusRecord {
|
||||||
|
|
||||||
extension StatusRecord {
|
extension StatusRecord {
|
||||||
static let account = belongsTo(AccountRecord.self)
|
static let account = belongsTo(AccountRecord.self)
|
||||||
|
static let relationship = hasOne(Relationship.self,
|
||||||
|
through: Self.account,
|
||||||
|
using: AccountRecord.relationship)
|
||||||
static let accountMoved = hasOne(AccountRecord.self,
|
static let accountMoved = hasOne(AccountRecord.self,
|
||||||
through: Self.account,
|
through: Self.account,
|
||||||
using: AccountRecord.moved)
|
using: AccountRecord.moved)
|
||||||
|
@ -82,6 +85,10 @@ extension StatusRecord {
|
||||||
through: Self.reblogAccount,
|
through: Self.reblogAccount,
|
||||||
using: AccountRecord.moved)
|
using: AccountRecord.moved)
|
||||||
static let reblog = belongsTo(StatusRecord.self)
|
static let reblog = belongsTo(StatusRecord.self)
|
||||||
|
static let reblogRelationship = hasOne(
|
||||||
|
Relationship.self,
|
||||||
|
through: Self.reblog,
|
||||||
|
using: Self.relationship)
|
||||||
static let showContentToggle = hasOne(StatusShowContentToggle.self)
|
static let showContentToggle = hasOne(StatusShowContentToggle.self)
|
||||||
static let reblogShowContentToggle = hasOne(
|
static let reblogShowContentToggle = hasOne(
|
||||||
StatusShowContentToggle.self,
|
StatusShowContentToggle.self,
|
||||||
|
|
|
@ -40,12 +40,13 @@ extension TimelineItemsInfo {
|
||||||
CollectionItem.status(
|
CollectionItem.status(
|
||||||
.init(info: $0),
|
.init(info: $0),
|
||||||
.init(showContentToggled: $0.showContentToggled,
|
.init(showContentToggled: $0.showContentToggled,
|
||||||
showAttachmentsToggled: $0.showAttachmentsToggled))
|
showAttachmentsToggled: $0.showAttachmentsToggled),
|
||||||
|
$0.reblogRelationship ?? $0.relationship)
|
||||||
}
|
}
|
||||||
|
|
||||||
for loadMoreRecord in loadMoreRecords {
|
for loadMoreRecord in loadMoreRecords {
|
||||||
guard let index = timelineItems.firstIndex(where: {
|
guard let index = timelineItems.firstIndex(where: {
|
||||||
guard case let .status(status, _) = $0 else { return false }
|
guard case let .status(status, _, _) = $0 else { return false }
|
||||||
|
|
||||||
return loadMoreRecord.afterStatusId > status.id
|
return loadMoreRecord.afterStatusId > status.id
|
||||||
}) else { continue }
|
}) else { continue }
|
||||||
|
@ -66,7 +67,8 @@ extension TimelineItemsInfo {
|
||||||
.init(info: $0),
|
.init(info: $0),
|
||||||
.init(showContentToggled: $0.showContentToggled,
|
.init(showContentToggled: $0.showContentToggled,
|
||||||
showAttachmentsToggled: $0.showAttachmentsToggled,
|
showAttachmentsToggled: $0.showAttachmentsToggled,
|
||||||
isPinned: true))
|
isPinned: true),
|
||||||
|
$0.reblogRelationship ?? $0.relationship)
|
||||||
}),
|
}),
|
||||||
.init(items: timelineItems)]
|
.init(items: timelineItems)]
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -3,9 +3,9 @@
|
||||||
import Mastodon
|
import Mastodon
|
||||||
|
|
||||||
public enum CollectionItem: Hashable {
|
public enum CollectionItem: Hashable {
|
||||||
case status(Status, StatusConfiguration)
|
case status(Status, StatusConfiguration, Relationship?)
|
||||||
case loadMore(LoadMore)
|
case loadMore(LoadMore)
|
||||||
case account(Account, AccountConfiguration)
|
case account(Account, AccountConfiguration, Relationship?)
|
||||||
case notification(MastodonNotification, StatusConfiguration?)
|
case notification(MastodonNotification, StatusConfiguration?)
|
||||||
case conversation(Conversation)
|
case conversation(Conversation)
|
||||||
case tag(Tag)
|
case tag(Tag)
|
||||||
|
@ -46,11 +46,11 @@ public extension CollectionItem {
|
||||||
|
|
||||||
var itemId: Id? {
|
var itemId: Id? {
|
||||||
switch self {
|
switch self {
|
||||||
case let .status(status, _):
|
case let .status(status, _, _):
|
||||||
return status.id
|
return status.id
|
||||||
case .loadMore:
|
case .loadMore:
|
||||||
return nil
|
return nil
|
||||||
case let .account(account, _):
|
case let .account(account, _, _):
|
||||||
return account.id
|
return account.id
|
||||||
case let .notification(notification, _):
|
case let .notification(notification, _):
|
||||||
return notification.id
|
return notification.id
|
||||||
|
|
|
@ -35,13 +35,13 @@ extension CollectionItem {
|
||||||
|
|
||||||
func estimatedHeight(width: CGFloat, identityContext: IdentityContext) -> CGFloat {
|
func estimatedHeight(width: CGFloat, identityContext: IdentityContext) -> CGFloat {
|
||||||
switch self {
|
switch self {
|
||||||
case let .status(status, configuration):
|
case let .status(status, configuration, _):
|
||||||
return StatusView.estimatedHeight(
|
return StatusView.estimatedHeight(
|
||||||
width: width,
|
width: width,
|
||||||
identityContext: identityContext,
|
identityContext: identityContext,
|
||||||
status: status,
|
status: status,
|
||||||
configuration: configuration)
|
configuration: configuration)
|
||||||
case let .account(account, configuration):
|
case let .account(account, configuration, _):
|
||||||
return AccountView.estimatedHeight(width: width, account: account, configuration: configuration)
|
return AccountView.estimatedHeight(width: width, account: account, configuration: configuration)
|
||||||
case .loadMore:
|
case .loadMore:
|
||||||
return LoadMoreView.estimatedHeight
|
return LoadMoreView.estimatedHeight
|
||||||
|
@ -65,9 +65,9 @@ extension CollectionItem {
|
||||||
|
|
||||||
func mediaPrefetchURLs(identityContext: IdentityContext) -> Set<URL> {
|
func mediaPrefetchURLs(identityContext: IdentityContext) -> Set<URL> {
|
||||||
switch self {
|
switch self {
|
||||||
case let .status(status, _):
|
case let .status(status, _, _):
|
||||||
return status.mediaPrefetchURLs(identityContext: identityContext)
|
return status.mediaPrefetchURLs(identityContext: identityContext)
|
||||||
case let .account(account, _):
|
case let .account(account, _, _):
|
||||||
return account.mediaPrefetchURLs(identityContext: identityContext)
|
return account.mediaPrefetchURLs(identityContext: identityContext)
|
||||||
case let .notification(notification, _):
|
case let .notification(notification, _):
|
||||||
var urls = notification.account.mediaPrefetchURLs(identityContext: identityContext)
|
var urls = notification.account.mediaPrefetchURLs(identityContext: identityContext)
|
||||||
|
|
|
@ -9,6 +9,7 @@ import MastodonAPI
|
||||||
public struct AccountListService {
|
public struct AccountListService {
|
||||||
public let sections: AnyPublisher<[CollectionSection], Error>
|
public let sections: AnyPublisher<[CollectionSection], Error>
|
||||||
public let nextPageMaxId: AnyPublisher<String, Never>
|
public let nextPageMaxId: AnyPublisher<String, Never>
|
||||||
|
public let accountIdsForRelationships: AnyPublisher<Set<Account.Id>, Never>
|
||||||
public let navigationService: NavigationService
|
public let navigationService: NavigationService
|
||||||
public let canRefresh = false
|
public let canRefresh = false
|
||||||
|
|
||||||
|
@ -18,6 +19,7 @@ public struct AccountListService {
|
||||||
private let contentDatabase: ContentDatabase
|
private let contentDatabase: ContentDatabase
|
||||||
private let titleComponents: [String]?
|
private let titleComponents: [String]?
|
||||||
private let nextPageMaxIdSubject = PassthroughSubject<String, Never>()
|
private let nextPageMaxIdSubject = PassthroughSubject<String, Never>()
|
||||||
|
private let accountIdsForRelationshipsSubject = PassthroughSubject<Set<Account.Id>, Never>()
|
||||||
|
|
||||||
init(endpoint: AccountsEndpoint,
|
init(endpoint: AccountsEndpoint,
|
||||||
mastodonAPIClient: MastodonAPIClient,
|
mastodonAPIClient: MastodonAPIClient,
|
||||||
|
@ -28,9 +30,11 @@ public struct AccountListService {
|
||||||
self.contentDatabase = contentDatabase
|
self.contentDatabase = contentDatabase
|
||||||
self.titleComponents = titleComponents
|
self.titleComponents = titleComponents
|
||||||
sections = accountsSubject
|
sections = accountsSubject
|
||||||
.map { [.init(items: $0.map { CollectionItem.account($0, endpoint.configuration) })] }
|
.map { [.init(items: $0.map { CollectionItem.account($0, endpoint.configuration, nil) })] } // TODO: revisit
|
||||||
|
.removeDuplicates()
|
||||||
.eraseToAnyPublisher()
|
.eraseToAnyPublisher()
|
||||||
nextPageMaxId = nextPageMaxIdSubject.eraseToAnyPublisher()
|
nextPageMaxId = nextPageMaxIdSubject.eraseToAnyPublisher()
|
||||||
|
accountIdsForRelationships = accountIdsForRelationshipsSubject.eraseToAnyPublisher()
|
||||||
navigationService = NavigationService(mastodonAPIClient: mastodonAPIClient, contentDatabase: contentDatabase)
|
navigationService = NavigationService(mastodonAPIClient: mastodonAPIClient, contentDatabase: contentDatabase)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -51,6 +55,7 @@ extension AccountListService: CollectionService {
|
||||||
guard let maxId = $0.info.maxId else { return }
|
guard let maxId = $0.info.maxId else { return }
|
||||||
|
|
||||||
nextPageMaxIdSubject.send(maxId)
|
nextPageMaxIdSubject.send(maxId)
|
||||||
|
accountIdsForRelationshipsSubject.send(Set($0.result.map(\.id)))
|
||||||
})
|
})
|
||||||
.flatMap { contentDatabase.insert(accounts: $0.result) }
|
.flatMap { contentDatabase.insert(accounts: $0.result) }
|
||||||
.ignoreOutput()
|
.ignoreOutput()
|
||||||
|
|
|
@ -8,24 +8,17 @@ import MastodonAPI
|
||||||
|
|
||||||
public struct AccountService {
|
public struct AccountService {
|
||||||
public let account: Account
|
public let account: Account
|
||||||
public let relationship: Relationship?
|
|
||||||
public let identityProofs: [IdentityProof]
|
|
||||||
public let featuredTags: [FeaturedTag]
|
|
||||||
public let navigationService: NavigationService
|
public let navigationService: NavigationService
|
||||||
|
|
||||||
private let mastodonAPIClient: MastodonAPIClient
|
private let mastodonAPIClient: MastodonAPIClient
|
||||||
private let contentDatabase: ContentDatabase
|
private let contentDatabase: ContentDatabase
|
||||||
|
|
||||||
public init(account: Account,
|
public init(account: Account,
|
||||||
relationship: Relationship? = nil,
|
|
||||||
identityProofs: [IdentityProof] = [],
|
identityProofs: [IdentityProof] = [],
|
||||||
featuredTags: [FeaturedTag] = [],
|
featuredTags: [FeaturedTag] = [],
|
||||||
mastodonAPIClient: MastodonAPIClient,
|
mastodonAPIClient: MastodonAPIClient,
|
||||||
contentDatabase: ContentDatabase) {
|
contentDatabase: ContentDatabase) {
|
||||||
self.account = account
|
self.account = account
|
||||||
self.relationship = relationship
|
|
||||||
self.identityProofs = identityProofs
|
|
||||||
self.featuredTags = featuredTags
|
|
||||||
navigationService = NavigationService(mastodonAPIClient: mastodonAPIClient, contentDatabase: contentDatabase)
|
navigationService = NavigationService(mastodonAPIClient: mastodonAPIClient, contentDatabase: contentDatabase)
|
||||||
self.mastodonAPIClient = mastodonAPIClient
|
self.mastodonAPIClient = mastodonAPIClient
|
||||||
self.contentDatabase = contentDatabase
|
self.contentDatabase = contentDatabase
|
||||||
|
|
|
@ -6,6 +6,7 @@ import Mastodon
|
||||||
public protocol CollectionService {
|
public protocol CollectionService {
|
||||||
var sections: AnyPublisher<[CollectionSection], Error> { get }
|
var sections: AnyPublisher<[CollectionSection], Error> { get }
|
||||||
var nextPageMaxId: AnyPublisher<String, Never> { get }
|
var nextPageMaxId: AnyPublisher<String, Never> { get }
|
||||||
|
var accountIdsForRelationships: AnyPublisher<Set<Account.Id>, Never> { get }
|
||||||
var preferLastPresentIdOverNextPageMaxId: Bool { get }
|
var preferLastPresentIdOverNextPageMaxId: Bool { get }
|
||||||
var canRefresh: Bool { get }
|
var canRefresh: Bool { get }
|
||||||
var title: AnyPublisher<String, Never> { get }
|
var title: AnyPublisher<String, Never> { get }
|
||||||
|
@ -18,6 +19,8 @@ public protocol CollectionService {
|
||||||
extension CollectionService {
|
extension CollectionService {
|
||||||
public var nextPageMaxId: AnyPublisher<String, Never> { Empty().eraseToAnyPublisher() }
|
public var nextPageMaxId: AnyPublisher<String, Never> { Empty().eraseToAnyPublisher() }
|
||||||
|
|
||||||
|
public var accountIdsForRelationships: AnyPublisher<Set<Account.Id>, Never> { Empty().eraseToAnyPublisher() }
|
||||||
|
|
||||||
public var preferLastPresentIdOverNextPageMaxId: Bool { false }
|
public var preferLastPresentIdOverNextPageMaxId: Bool { false }
|
||||||
|
|
||||||
public var canRefresh: Bool { true }
|
public var canRefresh: Bool { true }
|
||||||
|
|
|
@ -114,6 +114,12 @@ public extension IdentityService {
|
||||||
.eraseToAnyPublisher()
|
.eraseToAnyPublisher()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func requestRelationships(ids: Set<Account.Id>) -> AnyPublisher<Never, Error> {
|
||||||
|
mastodonAPIClient.request(RelationshipsEndpoint.relationships(ids: Array(ids)))
|
||||||
|
.flatMap(contentDatabase.insert(relationships:))
|
||||||
|
.eraseToAnyPublisher()
|
||||||
|
}
|
||||||
|
|
||||||
func getMarker(_ markerTimeline: Marker.Timeline) -> AnyPublisher<Marker, Error> {
|
func getMarker(_ markerTimeline: Marker.Timeline) -> AnyPublisher<Marker, Error> {
|
||||||
mastodonAPIClient.request(MarkersEndpoint.get([markerTimeline]))
|
mastodonAPIClient.request(MarkersEndpoint.get([markerTimeline]))
|
||||||
.compactMap { $0[markerTimeline.rawValue] }
|
.compactMap { $0[markerTimeline.rawValue] }
|
||||||
|
|
|
@ -7,7 +7,7 @@ import Mastodon
|
||||||
import MastodonAPI
|
import MastodonAPI
|
||||||
|
|
||||||
public struct ProfileService {
|
public struct ProfileService {
|
||||||
public let accountServicePublisher: AnyPublisher<AccountService, Error>
|
public let profilePublisher: AnyPublisher<Profile, Error>
|
||||||
|
|
||||||
private let id: Account.Id
|
private let id: Account.Id
|
||||||
private let mastodonAPIClient: MastodonAPIClient
|
private let mastodonAPIClient: MastodonAPIClient
|
||||||
|
@ -34,26 +34,16 @@ public struct ProfileService {
|
||||||
self.mastodonAPIClient = mastodonAPIClient
|
self.mastodonAPIClient = mastodonAPIClient
|
||||||
self.contentDatabase = contentDatabase
|
self.contentDatabase = contentDatabase
|
||||||
|
|
||||||
var accountPublisher = contentDatabase.profilePublisher(id: id)
|
var profilePublisher = contentDatabase.profilePublisher(id: id)
|
||||||
|
|
||||||
if let account = account {
|
if let account = account {
|
||||||
accountPublisher = accountPublisher
|
profilePublisher = profilePublisher
|
||||||
.merge(with: Just(Profile(account: account)).setFailureType(to: Error.self))
|
.merge(with: Just(Profile(account: account)).setFailureType(to: Error.self))
|
||||||
.removeDuplicates()
|
.removeDuplicates()
|
||||||
.eraseToAnyPublisher()
|
.eraseToAnyPublisher()
|
||||||
}
|
}
|
||||||
|
|
||||||
accountServicePublisher = accountPublisher
|
self.profilePublisher = profilePublisher
|
||||||
.map {
|
|
||||||
AccountService(
|
|
||||||
account: $0.account,
|
|
||||||
relationship: $0.relationship,
|
|
||||||
identityProofs: $0.identityProofs,
|
|
||||||
featuredTags: $0.featuredTags,
|
|
||||||
mastodonAPIClient: mastodonAPIClient,
|
|
||||||
contentDatabase: contentDatabase)
|
|
||||||
}
|
|
||||||
.eraseToAnyPublisher()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ public struct TimelineService {
|
||||||
public let sections: AnyPublisher<[CollectionSection], Error>
|
public let sections: AnyPublisher<[CollectionSection], Error>
|
||||||
public let navigationService: NavigationService
|
public let navigationService: NavigationService
|
||||||
public let nextPageMaxId: AnyPublisher<String, Never>
|
public let nextPageMaxId: AnyPublisher<String, Never>
|
||||||
|
public let accountIdsForRelationships: AnyPublisher<Set<Account.Id>, Never>
|
||||||
public let title: AnyPublisher<String, Never>
|
public let title: AnyPublisher<String, Never>
|
||||||
public let titleLocalizationComponents: AnyPublisher<[String], Never>
|
public let titleLocalizationComponents: AnyPublisher<[String], Never>
|
||||||
|
|
||||||
|
@ -17,6 +18,7 @@ public struct TimelineService {
|
||||||
private let mastodonAPIClient: MastodonAPIClient
|
private let mastodonAPIClient: MastodonAPIClient
|
||||||
private let contentDatabase: ContentDatabase
|
private let contentDatabase: ContentDatabase
|
||||||
private let nextPageMaxIdSubject = PassthroughSubject<String, Never>()
|
private let nextPageMaxIdSubject = PassthroughSubject<String, Never>()
|
||||||
|
private let accountIdsForRelationshipsSubject = PassthroughSubject<Set<Account.Id>, Never>()
|
||||||
|
|
||||||
init(timeline: Timeline, mastodonAPIClient: MastodonAPIClient, contentDatabase: ContentDatabase) {
|
init(timeline: Timeline, mastodonAPIClient: MastodonAPIClient, contentDatabase: ContentDatabase) {
|
||||||
self.timeline = timeline
|
self.timeline = timeline
|
||||||
|
@ -25,6 +27,7 @@ public struct TimelineService {
|
||||||
sections = contentDatabase.timelinePublisher(timeline)
|
sections = contentDatabase.timelinePublisher(timeline)
|
||||||
navigationService = NavigationService(mastodonAPIClient: mastodonAPIClient, contentDatabase: contentDatabase)
|
navigationService = NavigationService(mastodonAPIClient: mastodonAPIClient, contentDatabase: contentDatabase)
|
||||||
nextPageMaxId = nextPageMaxIdSubject.eraseToAnyPublisher()
|
nextPageMaxId = nextPageMaxIdSubject.eraseToAnyPublisher()
|
||||||
|
accountIdsForRelationships = accountIdsForRelationshipsSubject.eraseToAnyPublisher()
|
||||||
|
|
||||||
switch timeline {
|
switch timeline {
|
||||||
case let .list(list):
|
case let .list(list):
|
||||||
|
@ -66,6 +69,10 @@ extension TimelineService: CollectionService {
|
||||||
if let maxId = $0.info.maxId {
|
if let maxId = $0.info.maxId {
|
||||||
nextPageMaxIdSubject.send(maxId)
|
nextPageMaxIdSubject.send(maxId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
accountIdsForRelationshipsSubject.send(
|
||||||
|
Set($0.result.map(\.account.id))
|
||||||
|
.union(Set($0.result.compactMap(\.reblog?.account.id))))
|
||||||
})
|
})
|
||||||
.flatMap { contentDatabase.insert(statuses: $0.result, timeline: timeline) }
|
.flatMap { contentDatabase.insert(statuses: $0.result, timeline: timeline) }
|
||||||
.eraseToAnyPublisher()
|
.eraseToAnyPublisher()
|
||||||
|
|
|
@ -8,6 +8,9 @@ import ServiceLayer
|
||||||
public final class AccountViewModel: ObservableObject {
|
public final class AccountViewModel: ObservableObject {
|
||||||
public let identityContext: IdentityContext
|
public let identityContext: IdentityContext
|
||||||
public internal(set) var configuration = CollectionItem.AccountConfiguration.withNote
|
public internal(set) var configuration = CollectionItem.AccountConfiguration.withNote
|
||||||
|
public internal(set) var relationship: Relationship?
|
||||||
|
public internal(set) var identityProofs = [IdentityProof]()
|
||||||
|
public internal(set) var featuredTags = [FeaturedTag]()
|
||||||
|
|
||||||
private let accountService: AccountService
|
private let accountService: AccountService
|
||||||
private let eventsSubject: PassthroughSubject<AnyPublisher<CollectionItemEvent, Error>, Never>
|
private let eventsSubject: PassthroughSubject<AnyPublisher<CollectionItemEvent, Error>, Never>
|
||||||
|
@ -44,12 +47,6 @@ public extension AccountViewModel {
|
||||||
|
|
||||||
var isLocked: Bool { accountService.account.locked }
|
var isLocked: Bool { accountService.account.locked }
|
||||||
|
|
||||||
var relationship: Relationship? { accountService.relationship }
|
|
||||||
|
|
||||||
var identityProofs: [IdentityProof] { accountService.identityProofs }
|
|
||||||
|
|
||||||
var featuredTags: [FeaturedTag] { accountService.featuredTags }
|
|
||||||
|
|
||||||
var fields: [Account.Field] { accountService.account.fields }
|
var fields: [Account.Field] { accountService.account.fields }
|
||||||
|
|
||||||
var note: NSAttributedString { accountService.account.note.attributed }
|
var note: NSAttributedString { accountService.account.note.attributed }
|
||||||
|
|
|
@ -42,6 +42,13 @@ public class CollectionItemsViewModel: ObservableObject {
|
||||||
.sink { [weak self] in self?.nextPageMaxId = $0 }
|
.sink { [weak self] in self?.nextPageMaxId = $0 }
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
|
|
||||||
|
collectionService.accountIdsForRelationships
|
||||||
|
.filter { !$0.isEmpty }
|
||||||
|
.flatMap(identityContext.service.requestRelationships(ids:))
|
||||||
|
.catch { _ in Empty().setFailureType(to: Never.self) }
|
||||||
|
.sink { _ in }
|
||||||
|
.store(in: &cancellables)
|
||||||
|
|
||||||
if let markerTimeline = collectionService.markerTimeline {
|
if let markerTimeline = collectionService.markerTimeline {
|
||||||
shouldRestorePositionOfLocalLastReadId =
|
shouldRestorePositionOfLocalLastReadId =
|
||||||
identityContext.appPreferences.positionBehavior(markerTimeline: markerTimeline) == .rememberPosition
|
identityContext.appPreferences.positionBehavior(markerTimeline: markerTimeline) == .rememberPosition
|
||||||
|
@ -134,14 +141,14 @@ extension CollectionItemsViewModel: CollectionViewModel {
|
||||||
let item = lastUpdate.sections[indexPath.section].items[indexPath.item]
|
let item = lastUpdate.sections[indexPath.section].items[indexPath.item]
|
||||||
|
|
||||||
switch item {
|
switch item {
|
||||||
case let .status(status, _):
|
case let .status(status, _, relationship):
|
||||||
send(event: .navigation(.collection(collectionService
|
send(event: .navigation(.collection(collectionService
|
||||||
.navigationService
|
.navigationService
|
||||||
.contextService(id: status.displayStatus.id))))
|
.contextService(id: status.displayStatus.id))))
|
||||||
case let .loadMore(loadMore):
|
case let .loadMore(loadMore):
|
||||||
lastSelectedLoadMore = loadMore
|
lastSelectedLoadMore = loadMore
|
||||||
(viewModel(indexPath: indexPath) as? LoadMoreViewModel)?.loadMore()
|
(viewModel(indexPath: indexPath) as? LoadMoreViewModel)?.loadMore()
|
||||||
case let .account(account, _):
|
case let .account(account, _, relationship):
|
||||||
send(event: .navigation(.profile(collectionService
|
send(event: .navigation(.profile(collectionService
|
||||||
.navigationService
|
.navigationService
|
||||||
.profileService(account: account))))
|
.profileService(account: account))))
|
||||||
|
@ -182,7 +189,7 @@ extension CollectionItemsViewModel: CollectionViewModel {
|
||||||
|
|
||||||
public func canSelect(indexPath: IndexPath) -> Bool {
|
public func canSelect(indexPath: IndexPath) -> Bool {
|
||||||
switch lastUpdate.sections[indexPath.section].items[indexPath.item] {
|
switch lastUpdate.sections[indexPath.section].items[indexPath.item] {
|
||||||
case let .status(_, configuration):
|
case let .status(_, configuration, _):
|
||||||
return !configuration.isContextParent
|
return !configuration.isContextParent
|
||||||
case .loadMore:
|
case .loadMore:
|
||||||
return !((viewModel(indexPath: indexPath) as? LoadMoreViewModel)?.loading ?? false)
|
return !((viewModel(indexPath: indexPath) as? LoadMoreViewModel)?.loading ?? false)
|
||||||
|
@ -197,7 +204,7 @@ extension CollectionItemsViewModel: CollectionViewModel {
|
||||||
let cachedViewModel = viewModelCache[item]
|
let cachedViewModel = viewModelCache[item]
|
||||||
|
|
||||||
switch item {
|
switch item {
|
||||||
case let .status(status, configuration):
|
case let .status(status, configuration, relationship):
|
||||||
let viewModel: StatusViewModel
|
let viewModel: StatusViewModel
|
||||||
|
|
||||||
if let cachedViewModel = cachedViewModel as? StatusViewModel {
|
if let cachedViewModel = cachedViewModel as? StatusViewModel {
|
||||||
|
@ -211,6 +218,7 @@ extension CollectionItemsViewModel: CollectionViewModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
viewModel.configuration = configuration
|
viewModel.configuration = configuration
|
||||||
|
viewModel.accountViewModel.relationship = relationship
|
||||||
|
|
||||||
return viewModel
|
return viewModel
|
||||||
case let .loadMore(loadMore):
|
case let .loadMore(loadMore):
|
||||||
|
@ -225,7 +233,7 @@ extension CollectionItemsViewModel: CollectionViewModel {
|
||||||
viewModelCache[item] = viewModel
|
viewModelCache[item] = viewModel
|
||||||
|
|
||||||
return viewModel
|
return viewModel
|
||||||
case let .account(account, configuration):
|
case let .account(account, configuration, relationship):
|
||||||
let viewModel: AccountViewModel
|
let viewModel: AccountViewModel
|
||||||
|
|
||||||
if let cachedViewModel = cachedViewModel as? AccountViewModel {
|
if let cachedViewModel = cachedViewModel as? AccountViewModel {
|
||||||
|
@ -239,6 +247,7 @@ extension CollectionItemsViewModel: CollectionViewModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
viewModel.configuration = configuration
|
viewModel.configuration = configuration
|
||||||
|
viewModel.relationship = relationship
|
||||||
|
|
||||||
return viewModel
|
return viewModel
|
||||||
case let .notification(notification, statusConfiguration):
|
case let .notification(notification, statusConfiguration):
|
||||||
|
@ -302,7 +311,7 @@ extension CollectionItemsViewModel: CollectionViewModel {
|
||||||
|
|
||||||
public func toggleExpandAll() {
|
public func toggleExpandAll() {
|
||||||
let statusIds = Set(lastUpdate.sections.map(\.items).reduce([], +).compactMap { item -> Status.Id? in
|
let statusIds = Set(lastUpdate.sections.map(\.items).reduce([], +).compactMap { item -> Status.Id? in
|
||||||
guard case let .status(status, _) = item else { return nil }
|
guard case let .status(status, _, _) = item else { return nil }
|
||||||
|
|
||||||
return status.id
|
return status.id
|
||||||
})
|
})
|
||||||
|
@ -388,7 +397,7 @@ private extension CollectionItemsViewModel {
|
||||||
if collectionService is ContextService,
|
if collectionService is ContextService,
|
||||||
lastUpdate.sections.isEmpty || lastUpdate.sections.map(\.items.count) == [0, 1, 0],
|
lastUpdate.sections.isEmpty || lastUpdate.sections.map(\.items.count) == [0, 1, 0],
|
||||||
let contextParent = newItems.first(where: {
|
let contextParent = newItems.first(where: {
|
||||||
guard case let .status(_, configuration) = $0 else { return false }
|
guard case let .status(_, configuration, _) = $0 else { return false }
|
||||||
|
|
||||||
return configuration.isContextParent // Maintain scroll position of parent after initial load of context
|
return configuration.isContextParent // Maintain scroll position of parent after initial load of context
|
||||||
}) {
|
}) {
|
||||||
|
@ -404,7 +413,7 @@ private extension CollectionItemsViewModel {
|
||||||
let direction = (viewModelCache[item] as? LoadMoreViewModel)?.direction,
|
let direction = (viewModelCache[item] as? LoadMoreViewModel)?.direction,
|
||||||
direction == .up,
|
direction == .up,
|
||||||
let statusAfterLoadMore = items.first(where: {
|
let statusAfterLoadMore = items.first(where: {
|
||||||
guard case let .status(status, _) = $0 else { return false }
|
guard case let .status(status, _, _) = $0 else { return false }
|
||||||
|
|
||||||
return status.id == loadMore.beforeStatusId
|
return status.id == loadMore.beforeStatusId
|
||||||
}) {
|
}) {
|
||||||
|
|
|
@ -30,11 +30,19 @@ final public class ProfileViewModel {
|
||||||
|
|
||||||
self.accountEventsSubject = accountEventsSubject
|
self.accountEventsSubject = accountEventsSubject
|
||||||
|
|
||||||
profileService.accountServicePublisher
|
profileService.profilePublisher
|
||||||
.map {
|
.map {
|
||||||
AccountViewModel(accountService: $0,
|
let vm = AccountViewModel(accountService: identityContext.service
|
||||||
|
.navigationService
|
||||||
|
.accountService(account: $0.account),
|
||||||
identityContext: identityContext,
|
identityContext: identityContext,
|
||||||
eventsSubject: accountEventsSubject)
|
eventsSubject: accountEventsSubject)
|
||||||
|
|
||||||
|
vm.relationship = $0.relationship
|
||||||
|
vm.identityProofs = $0.identityProofs
|
||||||
|
vm.featuredTags = $0.featuredTags
|
||||||
|
|
||||||
|
return vm
|
||||||
}
|
}
|
||||||
.assignErrorsToAlertItem(to: \.alertItem, on: self)
|
.assignErrorsToAlertItem(to: \.alertItem, on: self)
|
||||||
.assign(to: &$accountViewModel)
|
.assign(to: &$accountViewModel)
|
||||||
|
|
Loading…
Reference in New Issue