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