Begin removing MastodonStatus, MastodonUser and related from CoreData (IOS-176, IOS-189)
This commit is contained in:
parent
7d41820015
commit
36091e9628
|
@ -388,7 +388,6 @@
|
|||
DB9282B225F3222800823B15 /* PickServerEmptyStateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB9282B125F3222800823B15 /* PickServerEmptyStateView.swift */; };
|
||||
DB938EE62623F50700E5B6C1 /* ThreadViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB938EE52623F50700E5B6C1 /* ThreadViewController.swift */; };
|
||||
DB938EED2623F79B00E5B6C1 /* ThreadViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB938EEC2623F79B00E5B6C1 /* ThreadViewModel.swift */; };
|
||||
DB938F0326240EA300E5B6C1 /* CachedThreadViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB938F0226240EA300E5B6C1 /* CachedThreadViewModel.swift */; };
|
||||
DB938F0926240F3C00E5B6C1 /* RemoteThreadViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB938F0826240F3C00E5B6C1 /* RemoteThreadViewModel.swift */; };
|
||||
DB938F0F2624119800E5B6C1 /* ThreadViewModel+LoadThreadState.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB938F0E2624119800E5B6C1 /* ThreadViewModel+LoadThreadState.swift */; };
|
||||
DB938F1F2624382F00E5B6C1 /* ThreadViewModel+Diffable.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB938F1E2624382F00E5B6C1 /* ThreadViewModel+Diffable.swift */; };
|
||||
|
@ -451,7 +450,6 @@
|
|||
DBCBCBF4267CB070000F5B51 /* Decode85.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBCBCBF3267CB070000F5B51 /* Decode85.swift */; };
|
||||
DBCBED1726132DB500B49291 /* UserTimelineViewModel+Diffable.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBCBED1626132DB500B49291 /* UserTimelineViewModel+Diffable.swift */; };
|
||||
DBCC3B30261440A50045B23D /* UITabBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBCC3B2F261440A50045B23D /* UITabBarController.swift */; };
|
||||
DBCC3B8F26148F7B0045B23D /* CachedProfileViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBCC3B8E26148F7B0045B23D /* CachedProfileViewModel.swift */; };
|
||||
DBD376B2269302A4007FEC24 /* UITableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBD376B1269302A4007FEC24 /* UITableViewCell.swift */; };
|
||||
DBD5B1F827BCFD9D00BD6B38 /* DataSourceProvider+TableViewControllerNavigateable.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBD5B1F727BCFD9D00BD6B38 /* DataSourceProvider+TableViewControllerNavigateable.swift */; };
|
||||
DBD5B1FA27BD013700BD6B38 /* DataSourceProvider+StatusTableViewControllerNavigateable.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBD5B1F927BD013700BD6B38 /* DataSourceProvider+StatusTableViewControllerNavigateable.swift */; };
|
||||
|
@ -1102,7 +1100,6 @@
|
|||
DB9282B125F3222800823B15 /* PickServerEmptyStateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PickServerEmptyStateView.swift; sourceTree = "<group>"; };
|
||||
DB938EE52623F50700E5B6C1 /* ThreadViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadViewController.swift; sourceTree = "<group>"; };
|
||||
DB938EEC2623F79B00E5B6C1 /* ThreadViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadViewModel.swift; sourceTree = "<group>"; };
|
||||
DB938F0226240EA300E5B6C1 /* CachedThreadViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CachedThreadViewModel.swift; sourceTree = "<group>"; };
|
||||
DB938F0826240F3C00E5B6C1 /* RemoteThreadViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteThreadViewModel.swift; sourceTree = "<group>"; };
|
||||
DB938F0E2624119800E5B6C1 /* ThreadViewModel+LoadThreadState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ThreadViewModel+LoadThreadState.swift"; sourceTree = "<group>"; };
|
||||
DB938F1E2624382F00E5B6C1 /* ThreadViewModel+Diffable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ThreadViewModel+Diffable.swift"; sourceTree = "<group>"; };
|
||||
|
@ -1190,7 +1187,6 @@
|
|||
DBCBCBF3267CB070000F5B51 /* Decode85.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Decode85.swift; sourceTree = "<group>"; };
|
||||
DBCBED1626132DB500B49291 /* UserTimelineViewModel+Diffable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UserTimelineViewModel+Diffable.swift"; sourceTree = "<group>"; };
|
||||
DBCC3B2F261440A50045B23D /* UITabBarController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITabBarController.swift; sourceTree = "<group>"; };
|
||||
DBCC3B8E26148F7B0045B23D /* CachedProfileViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CachedProfileViewModel.swift; sourceTree = "<group>"; };
|
||||
DBD376B1269302A4007FEC24 /* UITableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITableViewCell.swift; sourceTree = "<group>"; };
|
||||
DBD5B1F727BCFD9D00BD6B38 /* DataSourceProvider+TableViewControllerNavigateable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DataSourceProvider+TableViewControllerNavigateable.swift"; sourceTree = "<group>"; };
|
||||
DBD5B1F927BD013700BD6B38 /* DataSourceProvider+StatusTableViewControllerNavigateable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DataSourceProvider+StatusTableViewControllerNavigateable.swift"; sourceTree = "<group>"; };
|
||||
|
@ -2663,7 +2659,6 @@
|
|||
DB938EEC2623F79B00E5B6C1 /* ThreadViewModel.swift */,
|
||||
DB938F1E2624382F00E5B6C1 /* ThreadViewModel+Diffable.swift */,
|
||||
DB938F0E2624119800E5B6C1 /* ThreadViewModel+LoadThreadState.swift */,
|
||||
DB938F0226240EA300E5B6C1 /* CachedThreadViewModel.swift */,
|
||||
DB938F0826240F3C00E5B6C1 /* RemoteThreadViewModel.swift */,
|
||||
DB0FCB7F27968F70006C02E2 /* MastodonStatusThreadViewModel.swift */,
|
||||
);
|
||||
|
@ -2758,7 +2753,6 @@
|
|||
DBFEEC97279BDC6A004F81DD /* About */,
|
||||
DB9D6BFE25E4F5940051B173 /* ProfileViewController.swift */,
|
||||
DBB5255D2611F07A002F1F29 /* ProfileViewModel.swift */,
|
||||
DBCC3B8E26148F7B0045B23D /* CachedProfileViewModel.swift */,
|
||||
DBAE3FAE26172FC0004B8251 /* RemoteProfileViewModel.swift */,
|
||||
DBB525632612C988002F1F29 /* MeProfileViewModel.swift */,
|
||||
);
|
||||
|
@ -3870,7 +3864,6 @@
|
|||
2D38F20825CD491300561493 /* DisposeBagCollectable.swift in Sources */,
|
||||
D8F9170D2A4B3C6F008A5370 /* AboutMastodonTableViewCell.swift in Sources */,
|
||||
DB697DE1278F5296004EF2F7 /* DataSourceFacade+Model.swift in Sources */,
|
||||
DBCC3B8F26148F7B0045B23D /* CachedProfileViewModel.swift in Sources */,
|
||||
DB4F097526A037F500D62E92 /* SearchHistoryViewModel.swift in Sources */,
|
||||
DB3EA8E9281B7A3700598866 /* DiscoveryCommunityViewModel.swift in Sources */,
|
||||
D87BFC8B291D5C6B00FEE264 /* MastodonLoginView.swift in Sources */,
|
||||
|
@ -3882,7 +3875,6 @@
|
|||
2AB5011C299243FB00346092 /* WidgetExtension.intentdefinition in Sources */,
|
||||
DB9F58F126EF512300E7BBE9 /* AccountListTableViewCell.swift in Sources */,
|
||||
2D76319F25C1521200929FB9 /* StatusSection.swift in Sources */,
|
||||
DB938F0326240EA300E5B6C1 /* CachedThreadViewModel.swift in Sources */,
|
||||
DBA5A53526F0A36A00CACBAA /* AddAccountTableViewCell.swift in Sources */,
|
||||
2D35237A26256D920031AF25 /* NotificationSection.swift in Sources */,
|
||||
DB98EB6927B21A7C0082E365 /* ReportResultActionTableViewCell.swift in Sources */,
|
||||
|
|
|
@ -7,11 +7,10 @@
|
|||
|
||||
import Foundation
|
||||
import MastodonSDK
|
||||
import CoreDataStack
|
||||
|
||||
enum DiscoveryItem: Hashable {
|
||||
case hashtag(Mastodon.Entity.Tag)
|
||||
case link(Mastodon.Entity.Link)
|
||||
case user(ManagedObjectRecord<MastodonUser>)
|
||||
case user(Mastodon.Entity.Account)
|
||||
case bottomLoader
|
||||
}
|
||||
|
|
|
@ -57,25 +57,22 @@ extension DiscoverySection {
|
|||
return cell
|
||||
case .user(let record):
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: ProfileCardTableViewCell.self), for: indexPath) as! ProfileCardTableViewCell
|
||||
context.managedObjectContext.performAndWait {
|
||||
guard let user = record.object(in: context.managedObjectContext) else { return }
|
||||
cell.configure(
|
||||
tableView: tableView,
|
||||
user: user,
|
||||
profileCardTableViewCellDelegate: configuration.profileCardTableViewCellDelegate
|
||||
)
|
||||
// bind familiarFollowers
|
||||
if let familiarFollowers = configuration.familiarFollowers {
|
||||
familiarFollowers
|
||||
.map { array in array.first(where: { $0.id == user.id }) }
|
||||
.assign(to: \.familiarFollowers, on: cell.profileCardView.viewModel)
|
||||
.store(in: &cell.disposeBag)
|
||||
} else {
|
||||
cell.profileCardView.viewModel.familiarFollowers = nil
|
||||
}
|
||||
// bind me
|
||||
cell.profileCardView.viewModel.relationshipViewModel.me = configuration.authContext.mastodonAuthenticationBox.authentication.user(in: context.managedObjectContext)
|
||||
cell.configure(
|
||||
tableView: tableView,
|
||||
user: record,
|
||||
profileCardTableViewCellDelegate: configuration.profileCardTableViewCellDelegate
|
||||
)
|
||||
// bind familiarFollowers
|
||||
if let familiarFollowers = configuration.familiarFollowers {
|
||||
familiarFollowers
|
||||
.map { array in array.first(where: { $0.id == record.id }) }
|
||||
.assign(to: \.familiarFollowers, on: cell.profileCardView.viewModel)
|
||||
.store(in: &cell.disposeBag)
|
||||
} else {
|
||||
cell.profileCardView.viewModel.familiarFollowers = nil
|
||||
}
|
||||
// bind me
|
||||
cell.profileCardView.viewModel.relationshipViewModel.me = configuration.authContext.mastodonAuthenticationBox.inMemoryCache.meAccount
|
||||
return cell
|
||||
case .bottomLoader:
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: TimelineBottomLoaderTableViewCell.self), for: indexPath) as! TimelineBottomLoaderTableViewCell
|
||||
|
|
|
@ -5,12 +5,11 @@
|
|||
// Created by sxiaojian on 2021/4/13.
|
||||
//
|
||||
|
||||
import CoreData
|
||||
import Foundation
|
||||
import CoreDataStack
|
||||
import MastodonSDK
|
||||
|
||||
enum NotificationItem: Hashable {
|
||||
case feed(record: ManagedObjectRecord<Feed>)
|
||||
case feedLoader(record: ManagedObjectRecord<Feed>)
|
||||
case feed(record: FeedItem)
|
||||
case feedLoader(record: FeedItem)
|
||||
case bottomLoader
|
||||
}
|
||||
|
|
|
@ -43,16 +43,13 @@ extension NotificationSection {
|
|||
switch item {
|
||||
case .feed(let record):
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: NotificationTableViewCell.self), for: indexPath) as! NotificationTableViewCell
|
||||
context.managedObjectContext.performAndWait {
|
||||
guard let feed = record.object(in: context.managedObjectContext) else { return }
|
||||
configure(
|
||||
context: context,
|
||||
tableView: tableView,
|
||||
cell: cell,
|
||||
viewModel: NotificationTableViewCell.ViewModel(value: .feed(feed)),
|
||||
configuration: configuration
|
||||
)
|
||||
}
|
||||
configure(
|
||||
context: context,
|
||||
tableView: tableView,
|
||||
cell: cell,
|
||||
viewModel: NotificationTableViewCell.ViewModel(value: .feed(record)),
|
||||
configuration: configuration
|
||||
)
|
||||
return cell
|
||||
case .feedLoader:
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: TimelineBottomLoaderTableViewCell.self), for: indexPath) as! TimelineBottomLoaderTableViewCell
|
||||
|
|
|
@ -6,13 +6,13 @@
|
|||
//
|
||||
|
||||
import Foundation
|
||||
import CoreDataStack
|
||||
import MastodonSDK
|
||||
|
||||
enum ReportItem: Hashable {
|
||||
case header(context: HeaderContext)
|
||||
case status(record: ManagedObjectRecord<Status>)
|
||||
case status(record: Mastodon.Entity.Status)
|
||||
case comment(context: CommentContext)
|
||||
case result(record: ManagedObjectRecord<MastodonUser>)
|
||||
case result(record: Mastodon.Entity.Account)
|
||||
case bottomLoader
|
||||
}
|
||||
|
||||
|
|
|
@ -6,13 +6,13 @@
|
|||
//
|
||||
|
||||
import Foundation
|
||||
import CoreDataStack
|
||||
import MastodonUI
|
||||
import MastodonSDK
|
||||
|
||||
enum StatusItem: Hashable {
|
||||
case feed(record: ManagedObjectRecord<Feed>)
|
||||
case feedLoader(record: ManagedObjectRecord<Feed>)
|
||||
case status(record: ManagedObjectRecord<Status>)
|
||||
case feed(record: FeedItem)
|
||||
case feedLoader(record: FeedItem)
|
||||
case status(record: Mastodon.Entity.Status)
|
||||
case thread(Thread)
|
||||
case topLoader
|
||||
case bottomLoader
|
||||
|
@ -24,7 +24,7 @@ extension StatusItem {
|
|||
case reply(context: Context)
|
||||
case leaf(context: Context)
|
||||
|
||||
public var record: ManagedObjectRecord<Status> {
|
||||
public var record: Mastodon.Entity.Status {
|
||||
switch self {
|
||||
case .root(let threadContext),
|
||||
.reply(let threadContext),
|
||||
|
@ -37,12 +37,12 @@ extension StatusItem {
|
|||
|
||||
extension StatusItem.Thread {
|
||||
class Context: Hashable {
|
||||
let status: ManagedObjectRecord<Status>
|
||||
let status: Mastodon.Entity.Status
|
||||
var displayUpperConversationLink: Bool
|
||||
var displayBottomConversationLink: Bool
|
||||
|
||||
init(
|
||||
status: ManagedObjectRecord<Status>,
|
||||
status: Mastodon.Entity.Status,
|
||||
displayUpperConversationLink: Bool = false,
|
||||
displayBottomConversationLink: Bool = false
|
||||
) {
|
||||
|
|
|
@ -6,8 +6,6 @@
|
|||
//
|
||||
|
||||
import Combine
|
||||
import CoreData
|
||||
import CoreDataStack
|
||||
import UIKit
|
||||
import AVKit
|
||||
import AlamofireImage
|
||||
|
@ -46,40 +44,31 @@ extension StatusSection {
|
|||
switch item {
|
||||
case .feed(let record):
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: StatusTableViewCell.self), for: indexPath) as! StatusTableViewCell
|
||||
context.managedObjectContext.performAndWait {
|
||||
guard let feed = record.object(in: context.managedObjectContext) else { return }
|
||||
configure(
|
||||
context: context,
|
||||
tableView: tableView,
|
||||
cell: cell,
|
||||
viewModel: StatusTableViewCell.ViewModel(value: .feed(feed)),
|
||||
configuration: configuration
|
||||
)
|
||||
}
|
||||
configure(
|
||||
context: context,
|
||||
tableView: tableView,
|
||||
cell: cell,
|
||||
viewModel: StatusTableViewCell.ViewModel(value: .feed(record)),
|
||||
configuration: configuration
|
||||
)
|
||||
return cell
|
||||
case .feedLoader(let record):
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: TimelineMiddleLoaderTableViewCell.self), for: indexPath) as! TimelineMiddleLoaderTableViewCell
|
||||
context.managedObjectContext.performAndWait {
|
||||
guard let feed = record.object(in: context.managedObjectContext) else { return }
|
||||
configure(
|
||||
cell: cell,
|
||||
feed: feed,
|
||||
configuration: configuration
|
||||
)
|
||||
}
|
||||
configure(
|
||||
cell: cell,
|
||||
feed: record,
|
||||
configuration: configuration
|
||||
)
|
||||
return cell
|
||||
case .status(let record):
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: StatusTableViewCell.self), for: indexPath) as! StatusTableViewCell
|
||||
context.managedObjectContext.performAndWait {
|
||||
guard let status = record.object(in: context.managedObjectContext) else { return }
|
||||
configure(
|
||||
context: context,
|
||||
tableView: tableView,
|
||||
cell: cell,
|
||||
viewModel: StatusTableViewCell.ViewModel(value: .status(status)),
|
||||
configuration: configuration
|
||||
)
|
||||
}
|
||||
configure(
|
||||
context: context,
|
||||
tableView: tableView,
|
||||
cell: cell,
|
||||
viewModel: StatusTableViewCell.ViewModel(value: .status(record)),
|
||||
configuration: configuration
|
||||
)
|
||||
return cell
|
||||
case .thread(let thread):
|
||||
let cell = dequeueConfiguredReusableCell(
|
||||
|
@ -124,30 +113,24 @@ extension StatusSection {
|
|||
switch configuration.thread {
|
||||
case .root(let threadContext):
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: StatusThreadRootTableViewCell.self), for: indexPath) as! StatusThreadRootTableViewCell
|
||||
managedObjectContext.performAndWait {
|
||||
guard let status = threadContext.status.object(in: managedObjectContext) else { return }
|
||||
StatusSection.configure(
|
||||
context: context,
|
||||
tableView: tableView,
|
||||
cell: cell,
|
||||
viewModel: StatusThreadRootTableViewCell.ViewModel(value: .status(status)),
|
||||
configuration: configuration.configuration
|
||||
)
|
||||
}
|
||||
StatusSection.configure(
|
||||
context: context,
|
||||
tableView: tableView,
|
||||
cell: cell,
|
||||
viewModel: StatusThreadRootTableViewCell.ViewModel(value: .status(threadContext.status)),
|
||||
configuration: configuration.configuration
|
||||
)
|
||||
return cell
|
||||
case .reply(let threadContext),
|
||||
.leaf(let threadContext):
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: StatusTableViewCell.self), for: indexPath) as! StatusTableViewCell
|
||||
managedObjectContext.performAndWait {
|
||||
guard let status = threadContext.status.object(in: managedObjectContext) else { return }
|
||||
StatusSection.configure(
|
||||
context: context,
|
||||
tableView: tableView,
|
||||
cell: cell,
|
||||
viewModel: StatusTableViewCell.ViewModel(value: .status(status)),
|
||||
configuration: configuration.configuration
|
||||
)
|
||||
}
|
||||
StatusSection.configure(
|
||||
context: context,
|
||||
tableView: tableView,
|
||||
cell: cell,
|
||||
viewModel: StatusTableViewCell.ViewModel(value: .status(threadContext.status)),
|
||||
configuration: configuration.configuration
|
||||
)
|
||||
return cell
|
||||
}
|
||||
}
|
||||
|
@ -161,12 +144,11 @@ extension StatusSection {
|
|||
authContext: AuthContext,
|
||||
statusView: StatusView
|
||||
) {
|
||||
let managedObjectContext = context.managedObjectContext
|
||||
statusView.pollTableViewDiffableDataSource = UITableViewDiffableDataSource<PollSection, PollItem>(tableView: statusView.pollTableView) { tableView, indexPath, item in
|
||||
switch item {
|
||||
case .history:
|
||||
return nil
|
||||
case .option(let record):
|
||||
return UITableViewCell()
|
||||
case .option(let record, let poll):
|
||||
// Fix cell reuse animation issue
|
||||
let cell: PollOptionTableViewCell = {
|
||||
let _cell = tableView.dequeueReusableCell(withIdentifier: String(describing: PollOptionTableViewCell.self) + "@\(indexPath.row)#\(indexPath.section)") as? PollOptionTableViewCell
|
||||
|
@ -176,53 +158,8 @@ extension StatusSection {
|
|||
|
||||
cell.pollOptionView.viewModel.authContext = authContext
|
||||
|
||||
managedObjectContext.performAndWait {
|
||||
guard let option = record.object(in: managedObjectContext) else {
|
||||
assertionFailure()
|
||||
return
|
||||
}
|
||||
|
||||
cell.pollOptionView.configure(pollOption: option)
|
||||
|
||||
// trigger update if needs
|
||||
let needsUpdatePoll: Bool = {
|
||||
// check first option in poll to trigger update poll only once
|
||||
guard
|
||||
let poll = option.poll,
|
||||
option.index == 0
|
||||
else { return false }
|
||||
cell.pollOptionView.configure(status: statusView.viewModel.originalStatus!, pollOption: record, poll: poll)
|
||||
|
||||
guard !poll.expired else {
|
||||
return false
|
||||
}
|
||||
|
||||
let now = Date()
|
||||
let timeIntervalSinceUpdate = now.timeIntervalSince(poll.updatedAt)
|
||||
#if DEBUG
|
||||
let autoRefreshTimeInterval: TimeInterval = 3 // speedup testing
|
||||
#else
|
||||
let autoRefreshTimeInterval: TimeInterval = 30
|
||||
#endif
|
||||
|
||||
guard timeIntervalSinceUpdate > autoRefreshTimeInterval else {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}()
|
||||
|
||||
if needsUpdatePoll {
|
||||
guard let poll = option.poll else { return }
|
||||
let pollRecord: ManagedObjectRecord<Poll> = .init(objectID: poll.objectID)
|
||||
Task { [weak context] in
|
||||
guard let context = context else { return }
|
||||
_ = try await context.apiService.poll(
|
||||
poll: pollRecord,
|
||||
authenticationBox: authContext.mastodonAuthenticationBox
|
||||
)
|
||||
}
|
||||
}
|
||||
} // end managedObjectContext.performAndWait
|
||||
return cell
|
||||
}
|
||||
}
|
||||
|
@ -319,7 +256,7 @@ extension StatusSection {
|
|||
|
||||
static func configure(
|
||||
cell: TimelineMiddleLoaderTableViewCell,
|
||||
feed: Feed,
|
||||
feed: FeedItem,
|
||||
configuration: Configuration
|
||||
) {
|
||||
cell.configure(
|
||||
|
|
|
@ -11,7 +11,7 @@ import CoreDataStack
|
|||
import MastodonSDK
|
||||
|
||||
enum UserItem: Hashable {
|
||||
case user(record: ManagedObjectRecord<MastodonUser>)
|
||||
case user(record: Mastodon.Entity.Account)
|
||||
case account(account: Mastodon.Entity.Account, relationship: Mastodon.Entity.Relationship?)
|
||||
case bottomLoader
|
||||
case bottomHeader(text: String)
|
||||
|
|
|
@ -6,13 +6,12 @@
|
|||
//
|
||||
|
||||
import UIKit
|
||||
import CoreData
|
||||
import CoreDataStack
|
||||
import MastodonCore
|
||||
import MastodonUI
|
||||
import MastodonMeta
|
||||
import MetaTextKit
|
||||
import Combine
|
||||
import MastodonSDK
|
||||
|
||||
enum UserSection: Hashable {
|
||||
case main
|
||||
|
@ -37,7 +36,7 @@ extension UserSection {
|
|||
case .account(let account, let relationship):
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: UserTableViewCell.self), for: indexPath) as! UserTableViewCell
|
||||
|
||||
guard let me = authContext.mastodonAuthenticationBox.authentication.user(in: context.managedObjectContext) else { return cell }
|
||||
guard let me = authContext.mastodonAuthenticationBox.inMemoryCache.meAccount else { return cell }
|
||||
|
||||
cell.userView.setButtonState(.loading)
|
||||
cell.configure(
|
||||
|
@ -53,14 +52,13 @@ extension UserSection {
|
|||
case .user(let record):
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: UserTableViewCell.self), for: indexPath) as! UserTableViewCell
|
||||
context.managedObjectContext.performAndWait {
|
||||
guard let user = record.object(in: context.managedObjectContext) else { return }
|
||||
configure(
|
||||
context: context,
|
||||
authContext: authContext,
|
||||
tableView: tableView,
|
||||
cell: cell,
|
||||
viewModel: UserTableViewCell.ViewModel(
|
||||
user: user,
|
||||
user: record,
|
||||
followedUsers: authContext.mastodonAuthenticationBox.inMemoryCache.$followingUserIds.eraseToAnyPublisher(),
|
||||
blockedUsers: authContext.mastodonAuthenticationBox.inMemoryCache.$blockedUserIds.eraseToAnyPublisher(),
|
||||
followRequestedUsers: authContext.mastodonAuthenticationBox.inMemoryCache.$followRequestedUserIDs.eraseToAnyPublisher()
|
||||
|
@ -94,7 +92,7 @@ extension UserSection {
|
|||
userTableViewCellDelegate: UserTableViewCellDelegate?
|
||||
) {
|
||||
cell.configure(
|
||||
me: authContext.mastodonAuthenticationBox.authentication.user(in: context.managedObjectContext),
|
||||
me: authContext.mastodonAuthenticationBox.inMemoryCache.meAccount,
|
||||
tableView: tableView,
|
||||
viewModel: viewModel,
|
||||
delegate: userTableViewCellDelegate
|
||||
|
|
|
@ -6,14 +6,13 @@
|
|||
//
|
||||
|
||||
import UIKit
|
||||
import CoreData
|
||||
import CoreDataStack
|
||||
import MastodonSDK
|
||||
import MastodonCore
|
||||
|
||||
extension DataSourceFacade {
|
||||
public static func responseToStatusBookmarkAction(
|
||||
provider: UIViewController & NeedsDependency & AuthContextProvider,
|
||||
status: ManagedObjectRecord<Status>
|
||||
status: Mastodon.Entity.Status
|
||||
) async throws {
|
||||
let selectionFeedbackGenerator = await UISelectionFeedbackGenerator()
|
||||
await selectionFeedbackGenerator.selectionChanged()
|
||||
|
|
|
@ -6,14 +6,13 @@
|
|||
//
|
||||
|
||||
import UIKit
|
||||
import CoreData
|
||||
import CoreDataStack
|
||||
import MastodonCore
|
||||
import MastodonSDK
|
||||
|
||||
extension DataSourceFacade {
|
||||
public static func responseToStatusFavoriteAction(
|
||||
provider: DataSourceProvider & AuthContextProvider,
|
||||
status: ManagedObjectRecord<Status>
|
||||
status: Mastodon.Entity.Status
|
||||
) async throws {
|
||||
let selectionFeedbackGenerator = await UISelectionFeedbackGenerator()
|
||||
await selectionFeedbackGenerator.selectionChanged()
|
||||
|
|
|
@ -6,8 +6,6 @@
|
|||
//
|
||||
|
||||
import UIKit
|
||||
import CoreDataStack
|
||||
import class CoreDataStack.Notification
|
||||
import MastodonCore
|
||||
import MastodonSDK
|
||||
import MastodonLocalization
|
||||
|
@ -15,7 +13,7 @@ import MastodonLocalization
|
|||
extension DataSourceFacade {
|
||||
static func responseToUserFollowAction(
|
||||
dependency: NeedsDependency & AuthContextProvider,
|
||||
user: ManagedObjectRecord<MastodonUser>
|
||||
user: Mastodon.Entity.Account
|
||||
) async throws {
|
||||
let selectionFeedbackGenerator = await UISelectionFeedbackGenerator()
|
||||
await selectionFeedbackGenerator.selectionChanged()
|
||||
|
@ -26,86 +24,61 @@ extension DataSourceFacade {
|
|||
)
|
||||
dependency.context.authenticationService.fetchFollowingAndBlockedAsync()
|
||||
}
|
||||
|
||||
static func responseToUserFollowAction(
|
||||
dependency: NeedsDependency & AuthContextProvider,
|
||||
user: Mastodon.Entity.Account
|
||||
) async throws {
|
||||
let selectionFeedbackGenerator = await UISelectionFeedbackGenerator()
|
||||
await selectionFeedbackGenerator.selectionChanged()
|
||||
|
||||
_ = try await dependency.context.apiService.toggleFollow(
|
||||
user: user,
|
||||
authenticationBox: dependency.authContext.mastodonAuthenticationBox
|
||||
)
|
||||
dependency.context.authenticationService.fetchFollowingAndBlockedAsync()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension DataSourceFacade {
|
||||
static func responseToUserFollowRequestAction(
|
||||
dependency: NeedsDependency & AuthContextProvider,
|
||||
notification: ManagedObjectRecord<Notification>,
|
||||
notification: Mastodon.Entity.Notification,
|
||||
query: Mastodon.API.Account.FollowRequestQuery
|
||||
) async throws {
|
||||
let selectionFeedbackGenerator = await UISelectionFeedbackGenerator()
|
||||
await selectionFeedbackGenerator.selectionChanged()
|
||||
|
||||
let managedObjectContext = dependency.context.managedObjectContext
|
||||
let _userID: MastodonUser.ID? = try await managedObjectContext.perform {
|
||||
guard let notification = notification.object(in: managedObjectContext) else { return nil }
|
||||
return notification.account.id
|
||||
}
|
||||
|
||||
guard let userID = _userID else {
|
||||
assertionFailure()
|
||||
throw APIService.APIError.implicit(.badRequest)
|
||||
}
|
||||
|
||||
let state: MastodonFollowRequestState = try await managedObjectContext.perform {
|
||||
guard let notification = notification.object(in: managedObjectContext) else { return .init(state: .none) }
|
||||
return notification.followRequestState
|
||||
}
|
||||
|
||||
guard state.state == .none else {
|
||||
return
|
||||
}
|
||||
|
||||
try? await managedObjectContext.performChanges {
|
||||
guard let notification = notification.object(in: managedObjectContext) else { return }
|
||||
switch query {
|
||||
case .accept:
|
||||
notification.transientFollowRequestState = .init(state: .isAccepting)
|
||||
case .reject:
|
||||
notification.transientFollowRequestState = .init(state: .isRejecting)
|
||||
}
|
||||
}
|
||||
// let state: MastodonFollowRequestState = try await managedObjectContext.perform {
|
||||
// guard let notification = notification.object(in: managedObjectContext) else { return .init(state: .none) }
|
||||
// return notification.followRequestState
|
||||
// }
|
||||
//
|
||||
// guard state.state == .none else {
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// try? await managedObjectContext.performChanges {
|
||||
// guard let notification = notification.object(in: managedObjectContext) else { return }
|
||||
// switch query {
|
||||
// case .accept:
|
||||
// notification.transientFollowRequestState = .init(state: .isAccepting)
|
||||
// case .reject:
|
||||
// notification.transientFollowRequestState = .init(state: .isRejecting)
|
||||
// }
|
||||
// }
|
||||
|
||||
do {
|
||||
_ = try await dependency.context.apiService.followRequest(
|
||||
userID: userID,
|
||||
userID: notification.account.id,
|
||||
query: query,
|
||||
authenticationBox: dependency.authContext.mastodonAuthenticationBox
|
||||
)
|
||||
} catch {
|
||||
// reset state when failure
|
||||
try? await managedObjectContext.performChanges {
|
||||
guard let notification = notification.object(in: managedObjectContext) else { return }
|
||||
notification.transientFollowRequestState = .init(state: .none)
|
||||
}
|
||||
// try? await managedObjectContext.performChanges {
|
||||
// guard let notification = notification.object(in: managedObjectContext) else { return }
|
||||
// notification.transientFollowRequestState = .init(state: .none)
|
||||
// }
|
||||
|
||||
if let error = error as? Mastodon.API.Error {
|
||||
switch error.httpResponseStatus {
|
||||
case .notFound:
|
||||
let backgroundManagedObjectContext = dependency.context.backgroundManagedObjectContext
|
||||
try await backgroundManagedObjectContext.performChanges {
|
||||
guard let notification = notification.object(in: backgroundManagedObjectContext) else { return }
|
||||
for feed in notification.feeds {
|
||||
backgroundManagedObjectContext.delete(feed)
|
||||
}
|
||||
backgroundManagedObjectContext.delete(notification)
|
||||
}
|
||||
break
|
||||
// let backgroundManagedObjectContext = dependency.context.backgroundManagedObjectContext
|
||||
// try await backgroundManagedObjectContext.performChanges {
|
||||
// guard let notification = notification.object(in: backgroundManagedObjectContext) else { return }
|
||||
// for feed in notification.feeds {
|
||||
// backgroundManagedObjectContext.delete(feed)
|
||||
// }
|
||||
// backgroundManagedObjectContext.delete(notification)
|
||||
// }
|
||||
default:
|
||||
let alertController = await UIAlertController(for: error, title: nil, preferredStyle: .alert)
|
||||
let okAction = await UIAlertAction(title: L10n.Common.Controls.Actions.ok, style: .default)
|
||||
|
@ -121,38 +94,38 @@ extension DataSourceFacade {
|
|||
return
|
||||
}
|
||||
|
||||
try? await managedObjectContext.performChanges {
|
||||
guard let notification = notification.object(in: managedObjectContext) else { return }
|
||||
switch query {
|
||||
case .accept:
|
||||
notification.transientFollowRequestState = .init(state: .isAccept)
|
||||
case .reject:
|
||||
// do nothing due to will delete notification
|
||||
break
|
||||
}
|
||||
}
|
||||
// try? await managedObjectContext.performChanges {
|
||||
// guard let notification = notification.object(in: managedObjectContext) else { return }
|
||||
// switch query {
|
||||
// case .accept:
|
||||
// notification.transientFollowRequestState = .init(state: .isAccept)
|
||||
// case .reject:
|
||||
// // do nothing due to will delete notification
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
|
||||
let backgroundManagedObjectContext = dependency.context.backgroundManagedObjectContext
|
||||
try? await backgroundManagedObjectContext.performChanges {
|
||||
guard let notification = notification.object(in: backgroundManagedObjectContext) else { return }
|
||||
switch query {
|
||||
case .accept:
|
||||
notification.followRequestState = .init(state: .isAccept)
|
||||
case .reject:
|
||||
// delete notification
|
||||
for feed in notification.feeds {
|
||||
backgroundManagedObjectContext.delete(feed)
|
||||
}
|
||||
backgroundManagedObjectContext.delete(notification)
|
||||
}
|
||||
}
|
||||
// let backgroundManagedObjectContext = dependency.context.backgroundManagedObjectContext
|
||||
// try? await backgroundManagedObjectContext.performChanges {
|
||||
// guard let notification = notification.object(in: backgroundManagedObjectContext) else { return }
|
||||
// switch query {
|
||||
// case .accept:
|
||||
// notification.followRequestState = .init(state: .isAccept)
|
||||
// case .reject:
|
||||
// // delete notification
|
||||
// for feed in notification.feeds {
|
||||
// backgroundManagedObjectContext.delete(feed)
|
||||
// }
|
||||
// backgroundManagedObjectContext.delete(notification)
|
||||
// }
|
||||
// }
|
||||
} // end func
|
||||
}
|
||||
|
||||
extension DataSourceFacade {
|
||||
static func responseToShowHideReblogAction(
|
||||
dependency: NeedsDependency & AuthContextProvider,
|
||||
user: ManagedObjectRecord<MastodonUser>
|
||||
user: Mastodon.Entity.Account
|
||||
) async throws {
|
||||
_ = try await dependency.context.apiService.toggleShowReblogs(
|
||||
for: user,
|
||||
|
|
|
@ -19,8 +19,6 @@ extension DataSourceFacade {
|
|||
switch tag {
|
||||
case .entity(let entity):
|
||||
await coordinateToHashtagScene(provider: provider, tag: entity)
|
||||
case .record(let record):
|
||||
await coordinateToHashtagScene(provider: provider, tag: record)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ import UIKit
|
|||
import CoreDataStack
|
||||
import MastodonUI
|
||||
import MastodonLocalization
|
||||
import MastodonSDK
|
||||
|
||||
extension DataSourceFacade {
|
||||
|
||||
|
@ -61,14 +62,13 @@ extension DataSourceFacade {
|
|||
@MainActor
|
||||
static func coordinateToMediaPreviewScene(
|
||||
dependency: NeedsDependency & MediaPreviewableViewController,
|
||||
status: ManagedObjectRecord<Status>,
|
||||
status: Mastodon.Entity.Status,
|
||||
previewContext: AttachmentPreviewContext
|
||||
) async throws {
|
||||
let managedObjectContext = dependency.context.managedObjectContext
|
||||
let attachments: [MastodonAttachment] = try await managedObjectContext.perform {
|
||||
guard let _status = status.object(in: managedObjectContext) else { return [] }
|
||||
let status = _status.reblog ?? _status
|
||||
return status.attachments
|
||||
let status = status.reblog ?? status
|
||||
return status.mastodonAttachments
|
||||
}
|
||||
|
||||
let thumbnails = await previewContext.thumbnails()
|
||||
|
|
|
@ -6,20 +6,19 @@
|
|||
//
|
||||
|
||||
import Foundation
|
||||
import CoreDataStack
|
||||
import MetaTextKit
|
||||
import MastodonCore
|
||||
import MastodonSDK
|
||||
|
||||
extension DataSourceFacade {
|
||||
|
||||
static func responseToMetaTextAction(
|
||||
provider: DataSourceProvider & AuthContextProvider,
|
||||
target: StatusTarget,
|
||||
status: ManagedObjectRecord<Status>,
|
||||
status: Mastodon.Entity.Status,
|
||||
meta: Meta
|
||||
) async throws {
|
||||
let _redirectRecord = await DataSourceFacade.status(
|
||||
managedObjectContext: provider.context.managedObjectContext,
|
||||
let _redirectRecord = DataSourceFacade.status(
|
||||
status: status,
|
||||
target: target
|
||||
)
|
||||
|
@ -35,7 +34,7 @@ extension DataSourceFacade {
|
|||
|
||||
static func responseToMetaTextAction(
|
||||
provider: DataSourceProvider & AuthContextProvider,
|
||||
status: ManagedObjectRecord<Status>,
|
||||
status: Mastodon.Entity.Status,
|
||||
meta: Meta
|
||||
) async {
|
||||
switch meta {
|
||||
|
|
|
@ -6,44 +6,14 @@
|
|||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
import CoreDataStack
|
||||
import MastodonUI
|
||||
import MastodonSDK
|
||||
|
||||
extension DataSourceFacade {
|
||||
static func status(
|
||||
managedObjectContext: NSManagedObjectContext,
|
||||
status: ManagedObjectRecord<Status>,
|
||||
status: Mastodon.Entity.Status,
|
||||
target: StatusTarget
|
||||
) async -> ManagedObjectRecord<Status>? {
|
||||
return try? await managedObjectContext.perform {
|
||||
guard let object = status.object(in: managedObjectContext) else { return nil }
|
||||
return DataSourceFacade.status(status: object, target: target)
|
||||
.flatMap { ManagedObjectRecord<Status>(objectID: $0.objectID) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension DataSourceFacade {
|
||||
static func author(
|
||||
managedObjectContext: NSManagedObjectContext,
|
||||
status: ManagedObjectRecord<Status>,
|
||||
target: StatusTarget
|
||||
) async -> ManagedObjectRecord<MastodonUser>? {
|
||||
return try? await managedObjectContext.perform {
|
||||
guard let object = status.object(in: managedObjectContext) else { return nil }
|
||||
return DataSourceFacade.status(status: object, target: target)
|
||||
.flatMap { $0.author }
|
||||
.flatMap { ManagedObjectRecord<MastodonUser>(objectID: $0.objectID) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension DataSourceFacade {
|
||||
static func status(
|
||||
status: Status,
|
||||
target: StatusTarget
|
||||
) -> Status? {
|
||||
) -> Mastodon.Entity.Status? {
|
||||
switch target {
|
||||
case .status:
|
||||
return status.reblog ?? status
|
||||
|
@ -52,3 +22,13 @@ extension DataSourceFacade {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension DataSourceFacade {
|
||||
static func author(
|
||||
status: Mastodon.Entity.Status,
|
||||
target: StatusTarget
|
||||
) -> Mastodon.Entity.Account? {
|
||||
DataSourceFacade.status(status: status, target: target)
|
||||
.flatMap { $0.account }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,13 +6,13 @@
|
|||
//
|
||||
|
||||
import UIKit
|
||||
import CoreDataStack
|
||||
import MastodonCore
|
||||
import MastodonSDK
|
||||
|
||||
extension DataSourceFacade {
|
||||
static func responseToUserMuteAction(
|
||||
dependency: NeedsDependency & AuthContextProvider,
|
||||
user: ManagedObjectRecord<MastodonUser>
|
||||
user: Mastodon.Entity.Account
|
||||
) async throws {
|
||||
let selectionFeedbackGenerator = await UISelectionFeedbackGenerator()
|
||||
await selectionFeedbackGenerator.selectionChanged()
|
||||
|
|
|
@ -6,19 +6,18 @@
|
|||
//
|
||||
|
||||
import UIKit
|
||||
import CoreDataStack
|
||||
import MastodonCore
|
||||
import MastodonSDK
|
||||
import CoreDataStack
|
||||
|
||||
extension DataSourceFacade {
|
||||
|
||||
static func coordinateToProfileScene(
|
||||
provider: DataSourceProvider & AuthContextProvider,
|
||||
target: StatusTarget,
|
||||
status: ManagedObjectRecord<Status>
|
||||
status: Mastodon.Entity.Status
|
||||
) async {
|
||||
let _redirectRecord = await DataSourceFacade.author(
|
||||
managedObjectContext: provider.context.managedObjectContext,
|
||||
let _redirectRecord = DataSourceFacade.author(
|
||||
status: status,
|
||||
target: target
|
||||
)
|
||||
|
@ -35,17 +34,12 @@ extension DataSourceFacade {
|
|||
@MainActor
|
||||
static func coordinateToProfileScene(
|
||||
provider: ViewControllerWithDependencies & AuthContextProvider,
|
||||
user: ManagedObjectRecord<MastodonUser>
|
||||
user: Mastodon.Entity.Account
|
||||
) async {
|
||||
guard let user = user.object(in: provider.context.managedObjectContext) else {
|
||||
assertionFailure()
|
||||
return
|
||||
}
|
||||
|
||||
let profileViewModel = CachedProfileViewModel(
|
||||
let profileViewModel = ProfileViewModel(
|
||||
context: provider.context,
|
||||
authContext: provider.authContext,
|
||||
mastodonUser: user
|
||||
optionalMastodonUser: user
|
||||
)
|
||||
|
||||
_ = provider.coordinator.present(
|
||||
|
@ -71,9 +65,8 @@ extension DataSourceFacade {
|
|||
authenticationBox: provider.authContext.mastodonAuthenticationBox)
|
||||
provider.coordinator.hideLoading()
|
||||
|
||||
if let user {
|
||||
await coordinateToProfileScene(provider: provider, user: user.asRecord)
|
||||
}
|
||||
await coordinateToProfileScene(provider: provider, user: user)
|
||||
|
||||
} catch {
|
||||
provider.coordinator.hideLoading()
|
||||
}
|
||||
|
@ -85,12 +78,10 @@ extension DataSourceFacade {
|
|||
|
||||
static func coordinateToProfileScene(
|
||||
provider: DataSourceProvider & AuthContextProvider,
|
||||
status: ManagedObjectRecord<Status>,
|
||||
status: Mastodon.Entity.Status,
|
||||
mention: String, // username,
|
||||
userInfo: [AnyHashable: Any]?
|
||||
) async {
|
||||
let domain = provider.authContext.mastodonAuthenticationBox.domain
|
||||
|
||||
guard
|
||||
let href = userInfo?["href"] as? String,
|
||||
let url = URL(string: href)
|
||||
|
@ -98,10 +89,7 @@ extension DataSourceFacade {
|
|||
return
|
||||
}
|
||||
|
||||
let managedObjectContext = provider.context.managedObjectContext
|
||||
let mentions = try? await managedObjectContext.perform {
|
||||
return status.object(in: managedObjectContext)?.mentions ?? []
|
||||
}
|
||||
let mentions = status.mentions
|
||||
|
||||
guard let mention = mentions?.first(where: { $0.url == href }) else {
|
||||
_ = await provider.coordinator.present(
|
||||
|
@ -119,16 +107,7 @@ extension DataSourceFacade {
|
|||
return MeProfileViewModel(context: provider.context, authContext: provider.authContext)
|
||||
}
|
||||
|
||||
let request = MastodonUser.sortedFetchRequest
|
||||
request.fetchLimit = 1
|
||||
request.predicate = MastodonUser.predicate(domain: domain, id: userID)
|
||||
let _user = provider.context.managedObjectContext.safeFetch(request).first
|
||||
|
||||
if let user = _user {
|
||||
return CachedProfileViewModel(context: provider.context, authContext: provider.authContext, mastodonUser: user)
|
||||
} else {
|
||||
return RemoteProfileViewModel(context: provider.context, authContext: provider.authContext, userID: userID)
|
||||
}
|
||||
return RemoteProfileViewModel(context: provider.context, authContext: provider.authContext, userID: userID)
|
||||
}()
|
||||
|
||||
_ = await provider.coordinator.present(
|
||||
|
@ -154,11 +133,10 @@ extension DataSourceFacade {
|
|||
|
||||
static func createActivityViewController(
|
||||
dependency: NeedsDependency,
|
||||
user: ManagedObjectRecord<MastodonUser>
|
||||
user: Mastodon.Entity.Account
|
||||
) async throws -> UIActivityViewController? {
|
||||
let managedObjectContext = dependency.context.managedObjectContext
|
||||
let activityItems: [Any] = try await managedObjectContext.perform {
|
||||
guard let user = user.object(in: managedObjectContext) else { return [] }
|
||||
return user.activityItems
|
||||
}
|
||||
guard !activityItems.isEmpty else {
|
||||
|
@ -173,7 +151,7 @@ extension DataSourceFacade {
|
|||
return activityViewController
|
||||
}
|
||||
|
||||
static func createActivityViewControllerForMastodonUser(status: Status, dependency: NeedsDependency) -> UIActivityViewController {
|
||||
static func createActivityViewControllerForMastodonUser(status: Mastodon.Entity.Status, dependency: NeedsDependency) -> UIActivityViewController {
|
||||
let activityViewController = UIActivityViewController(
|
||||
activityItems: status.activityItems,
|
||||
applicationActivities: [SafariActivity(sceneCoordinator: dependency.coordinator)]
|
||||
|
|
|
@ -6,14 +6,14 @@
|
|||
//
|
||||
|
||||
import UIKit
|
||||
import CoreDataStack
|
||||
import MastodonCore
|
||||
import MastodonUI
|
||||
import MastodonSDK
|
||||
|
||||
extension DataSourceFacade {
|
||||
static func responseToStatusReblogAction(
|
||||
provider: DataSourceProvider & AuthContextProvider,
|
||||
status: ManagedObjectRecord<Status>
|
||||
status: Mastodon.Entity.Status
|
||||
) async throws {
|
||||
let selectionFeedbackGenerator = await UISelectionFeedbackGenerator()
|
||||
await selectionFeedbackGenerator.selectionChanged()
|
||||
|
|
|
@ -22,71 +22,60 @@ extension DataSourceFacade {
|
|||
break // not create search history for status
|
||||
case .user(let record):
|
||||
let authenticationBox = provider.authContext.mastodonAuthenticationBox
|
||||
let managedObjectContext = provider.context.backgroundManagedObjectContext
|
||||
|
||||
try? await managedObjectContext.performChanges {
|
||||
guard let me = authenticationBox.authentication.user(in: managedObjectContext) else { return }
|
||||
guard let user = record.object(in: managedObjectContext) else { return }
|
||||
_ = Persistence.SearchHistory.createOrMerge(
|
||||
in: managedObjectContext,
|
||||
context: Persistence.SearchHistory.PersistContext(
|
||||
entity: .user(user),
|
||||
me: me,
|
||||
now: Date()
|
||||
)
|
||||
)
|
||||
} // end try? await managedObjectContext.performChanges { … }
|
||||
assertionFailure("Implement storing search history")
|
||||
case .hashtag(let tag):
|
||||
let authenticationBox = provider.authContext.mastodonAuthenticationBox
|
||||
let managedObjectContext = provider.context.backgroundManagedObjectContext
|
||||
|
||||
switch tag {
|
||||
case .entity(let entity):
|
||||
try? await managedObjectContext.performChanges {
|
||||
guard let me = authenticationBox.authentication.user(in: managedObjectContext) else { return }
|
||||
|
||||
let now = Date()
|
||||
|
||||
let result = Persistence.Tag.createOrMerge(
|
||||
in: managedObjectContext,
|
||||
context: Persistence.Tag.PersistContext(
|
||||
domain: authenticationBox.domain,
|
||||
entity: entity,
|
||||
me: me,
|
||||
networkDate: now
|
||||
)
|
||||
)
|
||||
|
||||
_ = Persistence.SearchHistory.createOrMerge(
|
||||
in: managedObjectContext,
|
||||
context: Persistence.SearchHistory.PersistContext(
|
||||
entity: .hashtag(result.tag),
|
||||
me: me,
|
||||
now: now
|
||||
)
|
||||
)
|
||||
} // end try? await managedObjectContext.performChanges { … }
|
||||
case .record(let record):
|
||||
try? await managedObjectContext.performChanges {
|
||||
let authenticationBox = provider.authContext.mastodonAuthenticationBox
|
||||
guard let me = authenticationBox.authentication.user(in: managedObjectContext) else { return }
|
||||
guard let tag = record.object(in: managedObjectContext) else { return }
|
||||
|
||||
let now = Date()
|
||||
assertionFailure("Implement storing search history")
|
||||
|
||||
_ = Persistence.SearchHistory.createOrMerge(
|
||||
in: managedObjectContext,
|
||||
context: Persistence.SearchHistory.PersistContext(
|
||||
entity: .hashtag(tag),
|
||||
me: me,
|
||||
now: now
|
||||
)
|
||||
)
|
||||
} // end try? await managedObjectContext.performChanges { … }
|
||||
// try? await managedObjectContext.performChanges {
|
||||
// guard let me = authenticationBox.authentication.user(in: managedObjectContext) else { return }
|
||||
//
|
||||
// let now = Date()
|
||||
//
|
||||
// let result = Persistence.Tag.createOrMerge(
|
||||
// in: managedObjectContext,
|
||||
// context: Persistence.Tag.PersistContext(
|
||||
// domain: authenticationBox.domain,
|
||||
// entity: entity,
|
||||
// me: me,
|
||||
// networkDate: now
|
||||
// )
|
||||
// )
|
||||
//
|
||||
// _ = Persistence.SearchHistory.createOrMerge(
|
||||
// in: managedObjectContext,
|
||||
// context: Persistence.SearchHistory.PersistContext(
|
||||
// entity: .hashtag(result.tag),
|
||||
// me: me,
|
||||
// now: now
|
||||
// )
|
||||
// )
|
||||
// } // end try? await managedObjectContext.performChanges { … }
|
||||
// case .record(let record):
|
||||
// try? await managedObjectContext.performChanges {
|
||||
// let authenticationBox = provider.authContext.mastodonAuthenticationBox
|
||||
// guard let me = authenticationBox.authentication.user(in: managedObjectContext) else { return }
|
||||
// guard let tag = record.object(in: managedObjectContext) else { return }
|
||||
//
|
||||
// let now = Date()
|
||||
//
|
||||
// _ = Persistence.SearchHistory.createOrMerge(
|
||||
// in: managedObjectContext,
|
||||
// context: Persistence.SearchHistory.PersistContext(
|
||||
// entity: .hashtag(tag),
|
||||
// me: me,
|
||||
// now: now
|
||||
// )
|
||||
// )
|
||||
// } // end try? await managedObjectContext.performChanges { … }
|
||||
} // end switch tag { … }
|
||||
case .notification:
|
||||
assertionFailure()
|
||||
} // end switch item { … }
|
||||
} //end switch item { … }
|
||||
} // end func
|
||||
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ import CoreDataStack
|
|||
|
||||
extension DataSourceFacade {
|
||||
public static func getEditHistory(
|
||||
forStatus status: Status,
|
||||
forStatus status: Mastodon.Entity.Status,
|
||||
provider: NeedsDependency & AuthContextProvider
|
||||
) async throws -> [Mastodon.Entity.StatusEdit] {
|
||||
let reponse = try await provider.context.apiService.getHistory(forStatusID: status.id, authenticationBox: provider.authContext.mastodonAuthenticationBox)
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
//
|
||||
|
||||
import UIKit
|
||||
import CoreDataStack
|
||||
import Alamofire
|
||||
import AlamofireImage
|
||||
import MastodonCore
|
||||
|
@ -14,13 +13,14 @@ import MastodonUI
|
|||
import MastodonLocalization
|
||||
import LinkPresentation
|
||||
import UniformTypeIdentifiers
|
||||
import MastodonSDK
|
||||
|
||||
// Delete
|
||||
extension DataSourceFacade {
|
||||
|
||||
static func responseToDeleteStatus(
|
||||
dependency: NeedsDependency & AuthContextProvider,
|
||||
status: ManagedObjectRecord<Status>
|
||||
status: Mastodon.Entity.Status
|
||||
) async throws {
|
||||
_ = try await dependency.context.apiService.deleteStatus(
|
||||
status: status,
|
||||
|
@ -36,7 +36,7 @@ extension DataSourceFacade {
|
|||
@MainActor
|
||||
public static func responseToStatusShareAction(
|
||||
provider: DataSourceProvider,
|
||||
status: ManagedObjectRecord<Status>,
|
||||
status: Mastodon.Entity.Status,
|
||||
button: UIButton
|
||||
) async throws {
|
||||
let activityViewController = try await createActivityViewController(
|
||||
|
@ -56,22 +56,21 @@ extension DataSourceFacade {
|
|||
|
||||
private static func createActivityViewController(
|
||||
dependency: NeedsDependency,
|
||||
status: ManagedObjectRecord<Status>
|
||||
status: Mastodon.Entity.Status
|
||||
) async throws -> UIActivityViewController {
|
||||
var activityItems: [Any] = try await dependency.context.managedObjectContext.perform {
|
||||
guard let status = status.object(in: dependency.context.managedObjectContext),
|
||||
let url = URL(string: status.url ?? status.uri)
|
||||
var activityItems: [Any] = {
|
||||
guard let url = URL(string: status.url ?? status.uri)
|
||||
else { return [] }
|
||||
return [
|
||||
URLActivityItemWithMetadata(url: url) { metadata in
|
||||
metadata.title = "\(status.author.displayName) (@\(status.author.acctWithDomain))"
|
||||
metadata.title = "\(status.account.displayName) (@\(status.account.acctWithDomain))"
|
||||
metadata.iconProvider = ImageProvider(
|
||||
url: status.author.avatarImageURLWithFallback(domain: status.author.domain),
|
||||
url: status.account.avatarImageURLWithFallback(domain: status.account.domain!),
|
||||
filter: ScaledToSizeFilter(size: CGSize.authorAvatarButtonSize)
|
||||
).itemProvider
|
||||
}
|
||||
] as [Any]
|
||||
}
|
||||
}()
|
||||
var applicationActivities: [UIActivity] = [
|
||||
SafariActivity(sceneCoordinator: dependency.coordinator), // open URL
|
||||
]
|
||||
|
@ -94,20 +93,20 @@ extension DataSourceFacade {
|
|||
@MainActor
|
||||
static func responseToActionToolbar(
|
||||
provider: DataSourceProvider & AuthContextProvider,
|
||||
status: ManagedObjectRecord<Status>,
|
||||
status: Mastodon.Entity.Status,
|
||||
action: ActionToolbarContainer.Action,
|
||||
sender: UIButton
|
||||
) async throws {
|
||||
let managedObjectContext = provider.context.managedObjectContext
|
||||
let _status: ManagedObjectRecord<Status>? = try? await managedObjectContext.perform {
|
||||
guard let object = status.object(in: managedObjectContext) else { return nil }
|
||||
let objectID = (object.reblog ?? object).objectID
|
||||
return .init(objectID: objectID)
|
||||
}
|
||||
guard let status = _status else {
|
||||
assertionFailure()
|
||||
return
|
||||
}
|
||||
// let managedObjectContext = provider.context.managedObjectContext
|
||||
// let _status: ManagedObjectRecord<Status>? = try? await managedObjectContext.perform {
|
||||
// guard let object = status.object(in: managedObjectContext) else { return nil }
|
||||
// let objectID = (object.reblog ?? object).objectID
|
||||
// return .init(objectID: objectID)
|
||||
// }
|
||||
// guard let status = _status else {
|
||||
// assertionFailure()
|
||||
// return
|
||||
// }
|
||||
|
||||
switch action {
|
||||
case .reply:
|
||||
|
@ -150,7 +149,7 @@ extension DataSourceFacade {
|
|||
extension DataSourceFacade {
|
||||
|
||||
struct MenuContext {
|
||||
let author: ManagedObjectRecord<MastodonUser>?
|
||||
let author: Mastodon.Entity.Account?
|
||||
let statusViewModel: StatusView.ViewModel?
|
||||
let button: UIButton?
|
||||
let barButtonItem: UIBarButtonItem?
|
||||
|
@ -178,17 +177,9 @@ extension DataSourceFacade {
|
|||
title: actionTitle,
|
||||
style: .destructive
|
||||
) { [weak dependency] _ in
|
||||
guard let dependency else { return }
|
||||
guard let dependency, let user = menuContext.author else { return }
|
||||
|
||||
Task {
|
||||
let managedObjectContext = dependency.context.managedObjectContext
|
||||
let _user: ManagedObjectRecord<MastodonUser>? = try? await managedObjectContext.perform {
|
||||
guard let user = menuContext.author?.object(in: managedObjectContext) else { return nil }
|
||||
return ManagedObjectRecord<MastodonUser>(objectID: user.objectID)
|
||||
}
|
||||
|
||||
guard let user = _user else { return }
|
||||
|
||||
try await DataSourceFacade.responseToShowHideReblogAction(
|
||||
dependency: dependency,
|
||||
user: user
|
||||
|
@ -214,12 +205,7 @@ extension DataSourceFacade {
|
|||
) { [weak dependency] _ in
|
||||
guard let dependency = dependency else { return }
|
||||
Task {
|
||||
let managedObjectContext = dependency.context.managedObjectContext
|
||||
let _user: ManagedObjectRecord<MastodonUser>? = try? await managedObjectContext.perform {
|
||||
guard let user = menuContext.author?.object(in: managedObjectContext) else { return nil }
|
||||
return ManagedObjectRecord<MastodonUser>(objectID: user.objectID)
|
||||
}
|
||||
guard let user = _user else { return }
|
||||
guard let user = menuContext.author else { return }
|
||||
try await DataSourceFacade.responseToUserMuteAction(
|
||||
dependency: dependency,
|
||||
user: user
|
||||
|
@ -242,12 +228,7 @@ extension DataSourceFacade {
|
|||
) { [weak dependency] _ in
|
||||
guard let dependency = dependency else { return }
|
||||
Task {
|
||||
let managedObjectContext = dependency.context.managedObjectContext
|
||||
let _user: ManagedObjectRecord<MastodonUser>? = try? await managedObjectContext.perform {
|
||||
guard let user = menuContext.author?.object(in: managedObjectContext) else { return nil }
|
||||
return ManagedObjectRecord<MastodonUser>(objectID: user.objectID)
|
||||
}
|
||||
guard let user = _user else { return }
|
||||
guard let user = menuContext.author else { return }
|
||||
try await DataSourceFacade.responseToUserBlockAction(
|
||||
dependency: dependency,
|
||||
user: user
|
||||
|
@ -266,7 +247,7 @@ extension DataSourceFacade {
|
|||
context: dependency.context,
|
||||
authContext: dependency.authContext,
|
||||
user: user,
|
||||
status: menuContext.statusViewModel?.originalStatus?.asRecord
|
||||
status: menuContext.statusViewModel?.originalStatus
|
||||
)
|
||||
|
||||
_ = dependency.coordinator.present(
|
||||
|
@ -297,7 +278,7 @@ extension DataSourceFacade {
|
|||
)
|
||||
case .bookmarkStatus:
|
||||
Task {
|
||||
guard let status = menuContext.statusViewModel?.originalStatus?.asRecord else {
|
||||
guard let status = menuContext.statusViewModel?.originalStatus else {
|
||||
assertionFailure()
|
||||
return
|
||||
}
|
||||
|
@ -308,13 +289,7 @@ extension DataSourceFacade {
|
|||
} // end Task
|
||||
case .shareStatus:
|
||||
Task {
|
||||
let managedObjectContext = dependency.context.managedObjectContext
|
||||
guard let status: ManagedObjectRecord<Status> = try? await managedObjectContext.perform(block: {
|
||||
guard let object = menuContext.statusViewModel?.originalStatus?.asRecord.object(in: managedObjectContext) else { return nil }
|
||||
let objectID = (object.reblog ?? object).objectID
|
||||
return .init(objectID: objectID)
|
||||
}) else {
|
||||
assertionFailure()
|
||||
guard let status = menuContext.statusViewModel?.originalStatus else {
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -344,7 +319,7 @@ extension DataSourceFacade {
|
|||
style: .destructive
|
||||
) { [weak dependency] _ in
|
||||
guard let dependency = dependency else { return }
|
||||
guard let status = menuContext.statusViewModel?.originalStatus?.asRecord else { return }
|
||||
guard let status = menuContext.statusViewModel?.originalStatus else { return }
|
||||
Task {
|
||||
try await DataSourceFacade.responseToDeleteStatus(
|
||||
dependency: dependency,
|
||||
|
@ -358,7 +333,7 @@ extension DataSourceFacade {
|
|||
dependency.present(alertController, animated: true)
|
||||
|
||||
case .translateStatus:
|
||||
guard let status = menuContext.statusViewModel?.originalStatus?.asRecord else { return }
|
||||
guard let status = menuContext.statusViewModel?.originalStatus else { return }
|
||||
|
||||
do {
|
||||
let translation = try await DataSourceFacade.translateStatus(provider: dependency,status: status)
|
||||
|
@ -371,7 +346,7 @@ extension DataSourceFacade {
|
|||
}
|
||||
case .editStatus:
|
||||
|
||||
guard let status = menuContext.statusViewModel?.originalStatus?.asRecord.object(in: dependency.context.managedObjectContext) else { return }
|
||||
guard let status = menuContext.statusViewModel?.originalStatus else { return }
|
||||
|
||||
let statusSource = try await dependency.context.apiService.getStatusSource(
|
||||
forStatusID: status.id,
|
||||
|
@ -402,13 +377,13 @@ extension DataSourceFacade {
|
|||
|
||||
static func responseToToggleSensitiveAction(
|
||||
dependency: NeedsDependency,
|
||||
status: ManagedObjectRecord<Status>
|
||||
status: Mastodon.Entity.Status
|
||||
) async throws {
|
||||
try await dependency.context.managedObjectContext.perform {
|
||||
guard let _status = status.object(in: dependency.context.managedObjectContext) else { return }
|
||||
let status = _status.reblog ?? _status
|
||||
status.update(isSensitiveToggled: !status.isSensitiveToggled)
|
||||
}
|
||||
// try await dependency.context.managedObjectContext.perform {
|
||||
// let _status = status.reblog ?? status
|
||||
// status.update(isSensitiveToggled: !_status.sensitiveToggled)
|
||||
// }
|
||||
assertionFailure("Net yet implemented :-(")
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -6,19 +6,17 @@
|
|||
//
|
||||
|
||||
import UIKit
|
||||
import CoreData
|
||||
import CoreDataStack
|
||||
import MastodonCore
|
||||
import MastodonSDK
|
||||
|
||||
extension DataSourceFacade {
|
||||
static func coordinateToStatusThreadScene(
|
||||
provider: ViewControllerWithDependencies & AuthContextProvider,
|
||||
target: StatusTarget,
|
||||
status: ManagedObjectRecord<Status>
|
||||
status: Mastodon.Entity.Status
|
||||
) async {
|
||||
let _root: StatusItem.Thread? = await {
|
||||
let _redirectRecord = await DataSourceFacade.status(
|
||||
managedObjectContext: provider.context.managedObjectContext,
|
||||
let _root: StatusItem.Thread? = {
|
||||
let _redirectRecord = DataSourceFacade.status(
|
||||
status: status,
|
||||
target: target
|
||||
)
|
||||
|
|
|
@ -6,8 +6,6 @@
|
|||
//
|
||||
|
||||
import UIKit
|
||||
import CoreData
|
||||
import CoreDataStack
|
||||
import MastodonCore
|
||||
import MastodonSDK
|
||||
|
||||
|
@ -20,27 +18,21 @@ extension DataSourceFacade {
|
|||
|
||||
public static func translateStatus(
|
||||
provider: Provider,
|
||||
status: ManagedObjectRecord<Status>
|
||||
status: Mastodon.Entity.Status
|
||||
) async throws -> Mastodon.Entity.Translation? {
|
||||
let selectionFeedbackGenerator = await UISelectionFeedbackGenerator()
|
||||
await selectionFeedbackGenerator.selectionChanged()
|
||||
|
||||
guard
|
||||
let status = status.object(in: provider.context.managedObjectContext)
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
|
||||
if let reblog = status.reblog {
|
||||
return try await translateStatus(provider: provider, status: reblog)
|
||||
return try await _translateStatus(provider: provider, status: reblog)
|
||||
} else {
|
||||
return try await translateStatus(provider: provider, status: status)
|
||||
return try await _translateStatus(provider: provider, status: status)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension DataSourceFacade {
|
||||
static func translateStatus(provider: Provider, status: Status) async throws -> Mastodon.Entity.Translation? {
|
||||
static func _translateStatus(provider: Provider, status: Mastodon.Entity.Status) async throws -> Mastodon.Entity.Translation? {
|
||||
do {
|
||||
let value = try await provider.context
|
||||
.apiService
|
||||
|
|
|
@ -9,11 +9,12 @@ import Foundation
|
|||
import CoreDataStack
|
||||
import MetaTextKit
|
||||
import MastodonCore
|
||||
import MastodonSDK
|
||||
|
||||
extension DataSourceFacade {
|
||||
static func responseToURLAction(
|
||||
provider: DataSourceProvider & AuthContextProvider,
|
||||
status: ManagedObjectRecord<Status>,
|
||||
status: Mastodon.Entity.Status,
|
||||
url: URL
|
||||
) async {
|
||||
let domain = provider.authContext.mastodonAuthenticationBox.domain
|
||||
|
|
|
@ -2,69 +2,10 @@
|
|||
|
||||
import Foundation
|
||||
import MastodonUI
|
||||
import CoreDataStack
|
||||
import MastodonCore
|
||||
import MastodonSDK
|
||||
|
||||
extension DataSourceFacade {
|
||||
static func responseToUserViewButtonAction(
|
||||
dependency: NeedsDependency & AuthContextProvider,
|
||||
user: ManagedObjectRecord<MastodonUser>,
|
||||
buttonState: UserView.ButtonState
|
||||
) async throws {
|
||||
switch buttonState {
|
||||
case .follow:
|
||||
try await DataSourceFacade.responseToUserFollowAction(
|
||||
dependency: dependency,
|
||||
user: user
|
||||
)
|
||||
|
||||
if let userObject = user.object(in: dependency.context.managedObjectContext) {
|
||||
dependency.authContext.mastodonAuthenticationBox.inMemoryCache.followingUserIds.append(userObject.id)
|
||||
}
|
||||
|
||||
case .request:
|
||||
try await DataSourceFacade.responseToUserFollowAction(
|
||||
dependency: dependency,
|
||||
user: user
|
||||
)
|
||||
|
||||
if let userObject = user.object(in: dependency.context.managedObjectContext) {
|
||||
dependency.authContext.mastodonAuthenticationBox.inMemoryCache.followRequestedUserIDs.append(userObject.id)
|
||||
}
|
||||
|
||||
case .unfollow:
|
||||
try await DataSourceFacade.responseToUserFollowAction(
|
||||
dependency: dependency,
|
||||
user: user
|
||||
)
|
||||
if let userObject = user.object(in: dependency.context.managedObjectContext) {
|
||||
dependency.authContext.mastodonAuthenticationBox.inMemoryCache.followingUserIds.removeAll(where: { $0 == userObject.id })
|
||||
}
|
||||
case .blocked:
|
||||
try await DataSourceFacade.responseToUserBlockAction(
|
||||
dependency: dependency,
|
||||
user: user
|
||||
)
|
||||
|
||||
if let userObject = user.object(in: dependency.context.managedObjectContext) {
|
||||
dependency.authContext.mastodonAuthenticationBox.inMemoryCache.blockedUserIds.append(userObject.id)
|
||||
}
|
||||
|
||||
case .pending:
|
||||
try await DataSourceFacade.responseToUserFollowAction(
|
||||
dependency: dependency,
|
||||
user: user
|
||||
)
|
||||
|
||||
if let userObject = user.object(in: dependency.context.managedObjectContext) {
|
||||
dependency.authContext.mastodonAuthenticationBox.inMemoryCache.followRequestedUserIDs.removeAll(where: { $0 == userObject.id })
|
||||
}
|
||||
case .none, .loading:
|
||||
break //no-op
|
||||
}
|
||||
}
|
||||
|
||||
static func responseToUserViewButtonAction(
|
||||
dependency: NeedsDependency & AuthContextProvider,
|
||||
user: Mastodon.Entity.Account,
|
||||
|
|
|
@ -10,6 +10,7 @@ import MetaTextKit
|
|||
import CoreDataStack
|
||||
import MastodonCore
|
||||
import MastodonUI
|
||||
import MastodonSDK
|
||||
|
||||
// MARK: - Notification AuthorMenuAction
|
||||
extension NotificationTableViewCellDelegate where Self: DataSourceProvider & AuthContextProvider {
|
||||
|
@ -30,20 +31,11 @@ extension NotificationTableViewCellDelegate where Self: DataSourceProvider & Aut
|
|||
return
|
||||
}
|
||||
|
||||
let _author: ManagedObjectRecord<MastodonUser>? = try await self.context.managedObjectContext.perform {
|
||||
guard let notification = notification.object(in: self.context.managedObjectContext) else { return nil }
|
||||
return .init(objectID: notification.account.objectID)
|
||||
}
|
||||
guard let author = _author else {
|
||||
assertionFailure()
|
||||
return
|
||||
}
|
||||
|
||||
try await DataSourceFacade.responseToMenuAction(
|
||||
dependency: self,
|
||||
action: action,
|
||||
menuContext: .init(
|
||||
author: author,
|
||||
author: notification.account,
|
||||
statusViewModel: nil,
|
||||
button: button,
|
||||
barButtonItem: nil
|
||||
|
@ -70,17 +62,9 @@ extension NotificationTableViewCellDelegate where Self: DataSourceProvider & Aut
|
|||
assertionFailure("only works for status data provider")
|
||||
return
|
||||
}
|
||||
let _author: ManagedObjectRecord<MastodonUser>? = try await self.context.managedObjectContext.perform {
|
||||
guard let notification = notification.object(in: self.context.managedObjectContext) else { return nil }
|
||||
return .init(objectID: notification.account.objectID)
|
||||
}
|
||||
guard let author = _author else {
|
||||
assertionFailure()
|
||||
return
|
||||
}
|
||||
await DataSourceFacade.coordinateToProfileScene(
|
||||
provider: self,
|
||||
user: author
|
||||
user: notification.account
|
||||
)
|
||||
} // end Task
|
||||
}
|
||||
|
@ -155,7 +139,7 @@ extension NotificationTableViewCellDelegate where Self: DataSourceProvider & Aut
|
|||
}
|
||||
|
||||
private struct NotificationMediaTransitionContext {
|
||||
let status: ManagedObjectRecord<Status>
|
||||
let status: Mastodon.Entity.Status
|
||||
let needsToggleMediaSensitive: Bool
|
||||
}
|
||||
|
||||
|
@ -175,23 +159,17 @@ extension NotificationTableViewCellDelegate where Self: DataSourceProvider & Med
|
|||
assertionFailure()
|
||||
return
|
||||
}
|
||||
guard case let .notification(record) = item else {
|
||||
guard case let .notification(record) = item, let _status = record.status else {
|
||||
assertionFailure("only works for status data provider")
|
||||
return
|
||||
}
|
||||
|
||||
let managedObjectContext = self.context.managedObjectContext
|
||||
let _mediaTransitionContext: NotificationMediaTransitionContext? = try await managedObjectContext.perform {
|
||||
guard let notification = record.object(in: managedObjectContext) else { return nil }
|
||||
guard let _status = notification.status else { return nil }
|
||||
let status = _status.reblog ?? _status
|
||||
return NotificationMediaTransitionContext(
|
||||
status: .init(objectID: status.objectID),
|
||||
needsToggleMediaSensitive: status.isSensitiveToggled ? !status.sensitive : status.sensitive
|
||||
)
|
||||
}
|
||||
|
||||
guard let mediaTransitionContext = _mediaTransitionContext else { return }
|
||||
let status = _status.reblog ?? _status
|
||||
let mediaTransitionContext = NotificationMediaTransitionContext(
|
||||
status: status,
|
||||
needsToggleMediaSensitive: status.sensitiveToggled ? !(status.sensitive == true) : status.sensitive == true
|
||||
)
|
||||
|
||||
guard !mediaTransitionContext.needsToggleMediaSensitive else {
|
||||
try await DataSourceFacade.responseToToggleSensitiveAction(
|
||||
|
@ -227,23 +205,18 @@ extension NotificationTableViewCellDelegate where Self: DataSourceProvider & Med
|
|||
assertionFailure()
|
||||
return
|
||||
}
|
||||
guard case let .notification(record) = item else {
|
||||
guard case let .notification(record) = item, let _status = record.status else {
|
||||
assertionFailure("only works for status data provider")
|
||||
return
|
||||
}
|
||||
|
||||
let managedObjectContext = self.context.managedObjectContext
|
||||
let _mediaTransitionContext: NotificationMediaTransitionContext? = try await managedObjectContext.perform {
|
||||
guard let notification = record.object(in: managedObjectContext) else { return nil }
|
||||
guard let _status = notification.status else { return nil }
|
||||
let status = _status.reblog ?? _status
|
||||
return NotificationMediaTransitionContext(
|
||||
status: .init(objectID: status.objectID),
|
||||
needsToggleMediaSensitive: status.isMediaSensitive ? !status.isSensitiveToggled : false
|
||||
)
|
||||
}
|
||||
|
||||
guard let mediaTransitionContext = _mediaTransitionContext else { return }
|
||||
|
||||
let status = _status.reblog ?? _status
|
||||
let mediaTransitionContext = NotificationMediaTransitionContext(
|
||||
status: status,
|
||||
needsToggleMediaSensitive: status.sensitiveToggled ? !status.sensitiveToggled : false
|
||||
)
|
||||
|
||||
guard !mediaTransitionContext.needsToggleMediaSensitive else {
|
||||
try await DataSourceFacade.responseToToggleSensitiveAction(
|
||||
|
@ -286,11 +259,8 @@ extension NotificationTableViewCellDelegate where Self: DataSourceProvider & Aut
|
|||
assertionFailure("only works for status data provider")
|
||||
return
|
||||
}
|
||||
let _status: ManagedObjectRecord<Status>? = try await self.context.managedObjectContext.perform {
|
||||
guard let notification = notification.object(in: self.context.managedObjectContext) else { return nil }
|
||||
guard let status = notification.status else { return nil }
|
||||
return .init(objectID: status.objectID)
|
||||
}
|
||||
let _status = notification.status
|
||||
|
||||
guard let status = _status else {
|
||||
assertionFailure()
|
||||
return
|
||||
|
@ -323,12 +293,8 @@ extension NotificationTableViewCellDelegate where Self: DataSourceProvider & Aut
|
|||
assertionFailure("only works for status data provider")
|
||||
return
|
||||
}
|
||||
let _author: ManagedObjectRecord<MastodonUser>? = try await self.context.managedObjectContext.perform {
|
||||
guard let notification = notification.object(in: self.context.managedObjectContext) else { return nil }
|
||||
guard let status = notification.status else { return nil }
|
||||
return .init(objectID: status.author.objectID)
|
||||
}
|
||||
guard let author = _author else {
|
||||
|
||||
guard let author = notification.status?.account else {
|
||||
assertionFailure()
|
||||
return
|
||||
}
|
||||
|
@ -367,12 +333,8 @@ extension NotificationTableViewCellDelegate where Self: DataSourceProvider & Aut
|
|||
assertionFailure("only works for notification item")
|
||||
return
|
||||
}
|
||||
let _status: ManagedObjectRecord<Status>? = try await self.context.managedObjectContext.perform {
|
||||
guard let notification = notification.object(in: self.context.managedObjectContext) else { return nil }
|
||||
guard let status = notification.status else { return nil }
|
||||
return .init(objectID: status.objectID)
|
||||
}
|
||||
guard let status = _status else {
|
||||
|
||||
guard let status = notification.status else {
|
||||
assertionFailure()
|
||||
return
|
||||
}
|
||||
|
@ -400,12 +362,8 @@ extension NotificationTableViewCellDelegate where Self: DataSourceProvider & Aut
|
|||
assertionFailure("only works for notification item")
|
||||
return
|
||||
}
|
||||
let _status: ManagedObjectRecord<Status>? = try await self.context.managedObjectContext.perform {
|
||||
guard let notification = notification.object(in: self.context.managedObjectContext) else { return nil }
|
||||
guard let status = notification.status else { return nil }
|
||||
return .init(objectID: status.objectID)
|
||||
}
|
||||
guard let status = _status else {
|
||||
|
||||
guard let status = notification.status else {
|
||||
assertionFailure()
|
||||
return
|
||||
}
|
||||
|
@ -465,12 +423,8 @@ extension NotificationTableViewCellDelegate where Self: DataSourceProvider & Aut
|
|||
assertionFailure("only works for notification item")
|
||||
return
|
||||
}
|
||||
let _status: ManagedObjectRecord<Status>? = try await self.context.managedObjectContext.perform {
|
||||
guard let notification = notification.object(in: self.context.managedObjectContext) else { return nil }
|
||||
guard let status = notification.status else { return nil }
|
||||
return .init(objectID: status.objectID)
|
||||
}
|
||||
guard let status = _status else {
|
||||
|
||||
guard let status = notification.status else {
|
||||
assertionFailure()
|
||||
return
|
||||
}
|
||||
|
@ -497,12 +451,8 @@ extension NotificationTableViewCellDelegate where Self: DataSourceProvider & Aut
|
|||
assertionFailure("only works for notification item")
|
||||
return
|
||||
}
|
||||
let _status: ManagedObjectRecord<Status>? = try await self.context.managedObjectContext.perform {
|
||||
guard let notification = notification.object(in: self.context.managedObjectContext) else { return nil }
|
||||
guard let status = notification.status else { return nil }
|
||||
return .init(objectID: status.objectID)
|
||||
}
|
||||
guard let status = _status else {
|
||||
|
||||
guard let status = notification.status else {
|
||||
assertionFailure()
|
||||
return
|
||||
}
|
||||
|
|
|
@ -6,13 +6,13 @@
|
|||
//
|
||||
|
||||
import UIKit
|
||||
import CoreDataStack
|
||||
import MetaTextKit
|
||||
import MastodonCore
|
||||
import MastodonUI
|
||||
import MastodonLocalization
|
||||
import MastodonAsset
|
||||
import LinkPresentation
|
||||
import MastodonSDK
|
||||
|
||||
// MARK: - header
|
||||
extension StatusTableViewCellDelegate where Self: DataSourceProvider & AuthContextProvider {
|
||||
|
@ -37,22 +37,15 @@ extension StatusTableViewCellDelegate where Self: DataSourceProvider & AuthConte
|
|||
case .none:
|
||||
break
|
||||
case .reply:
|
||||
let _replyToAuthor: ManagedObjectRecord<MastodonUser>? = try? await context.managedObjectContext.perform {
|
||||
guard let status = status.object(in: self.context.managedObjectContext) else { return nil }
|
||||
guard let inReplyToAccountID = status.inReplyToAccountID else { return nil }
|
||||
let request = MastodonUser.sortedFetchRequest
|
||||
request.predicate = MastodonUser.predicate(domain: status.author.domain, id: inReplyToAccountID)
|
||||
request.fetchLimit = 1
|
||||
guard let author = self.context.managedObjectContext.safeFetch(request).first else { return nil }
|
||||
return .init(objectID: author.objectID)
|
||||
}
|
||||
guard let replyToAuthor = _replyToAuthor else {
|
||||
return
|
||||
}
|
||||
let account = try await context.apiService.accountLookup(
|
||||
domain: authContext.mastodonAuthenticationBox.domain,
|
||||
query: .init(acct: status.account.id),
|
||||
authorization: authContext.mastodonAuthenticationBox.userAuthorization
|
||||
).singleOutput().value
|
||||
|
||||
await DataSourceFacade.coordinateToProfileScene(
|
||||
provider: self,
|
||||
user: replyToAuthor
|
||||
user: account
|
||||
)
|
||||
|
||||
case .repost:
|
||||
|
@ -184,7 +177,7 @@ extension StatusTableViewCellDelegate where Self: DataSourceProvider & AuthConte
|
|||
cardControlMenu statusCardControl: StatusCardControl
|
||||
) -> [LabeledAction]? {
|
||||
guard let card = statusView.viewModel.card,
|
||||
let url = card.url else {
|
||||
let url = URL(string: card.url) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -206,8 +199,8 @@ extension StatusTableViewCellDelegate where Self: DataSourceProvider & AuthConte
|
|||
URLActivityItemWithMetadata(url: url) { metadata in
|
||||
metadata.title = card.title
|
||||
|
||||
if let image = card.imageURL {
|
||||
metadata.iconProvider = ImageProvider(url: image, filter: nil).itemProvider
|
||||
if let image = card.image, let url = URL(string: image) {
|
||||
metadata.iconProvider = ImageProvider(url: url, filter: nil).itemProvider
|
||||
}
|
||||
}
|
||||
],
|
||||
|
@ -313,54 +306,51 @@ extension StatusTableViewCellDelegate where Self: DataSourceProvider & AuthConte
|
|||
return
|
||||
}
|
||||
|
||||
var _poll: ManagedObjectRecord<Poll>?
|
||||
var _isMultiple: Bool?
|
||||
var _choice: Int?
|
||||
|
||||
try await managedObjectContext.performChanges {
|
||||
guard let pollOption = pollOption.object(in: managedObjectContext) else { return }
|
||||
guard let poll = pollOption.poll else { return }
|
||||
_poll = .init(objectID: poll.objectID)
|
||||
|
||||
_isMultiple = poll.multiple
|
||||
guard !poll.isVoting else { return }
|
||||
|
||||
if !poll.multiple {
|
||||
for option in poll.options where option != pollOption {
|
||||
option.update(isSelected: false)
|
||||
}
|
||||
|
||||
// mark voting
|
||||
poll.update(isVoting: true)
|
||||
// set choice
|
||||
_choice = Int(pollOption.index)
|
||||
}
|
||||
|
||||
pollOption.update(isSelected: !pollOption.isSelected)
|
||||
poll.update(updatedAt: Date())
|
||||
}
|
||||
// var _poll: ManagedObjectRecord<Poll>?
|
||||
// var _isMultiple: Bool?
|
||||
// var _choice: Int?
|
||||
//
|
||||
// try await managedObjectContext.performChanges {
|
||||
// guard let pollOption = pollOption.object(in: managedObjectContext) else { return }
|
||||
// guard let poll = pollOption.poll else { return }
|
||||
// _poll = .init(objectID: poll.objectID)
|
||||
//
|
||||
// _isMultiple = poll.multiple
|
||||
// guard !poll.isVoting else { return }
|
||||
//
|
||||
// if !poll.multiple {
|
||||
// for option in poll.options where option != pollOption {
|
||||
// option.update(isSelected: false)
|
||||
// }
|
||||
//
|
||||
// // mark voting
|
||||
// poll.update(isVoting: true)
|
||||
// // set choice
|
||||
// _choice = Int(pollOption.index)
|
||||
// }
|
||||
//
|
||||
// pollOption.update(isSelected: !pollOption.isSelected)
|
||||
// poll.update(updatedAt: Date())
|
||||
// }
|
||||
|
||||
// Trigger vote API request for
|
||||
guard let poll = _poll,
|
||||
_isMultiple == false,
|
||||
let choice = _choice
|
||||
else { return }
|
||||
|
||||
guard pollOption.poll.multiple == false else { return }
|
||||
|
||||
do {
|
||||
_ = try await context.apiService.vote(
|
||||
poll: poll,
|
||||
choices: [choice],
|
||||
poll: pollOption.poll,
|
||||
choices: [indexPath.row],
|
||||
authenticationBox: authContext.mastodonAuthenticationBox
|
||||
)
|
||||
} catch {
|
||||
// restore voting state
|
||||
try await managedObjectContext.performChanges {
|
||||
guard
|
||||
let pollOption = pollOption.object(in: managedObjectContext),
|
||||
let poll = pollOption.poll
|
||||
else { return }
|
||||
poll.update(isVoting: false)
|
||||
}
|
||||
// try await managedObjectContext.performChanges {
|
||||
// guard
|
||||
// let pollOption = pollOption.object(in: managedObjectContext),
|
||||
// let poll = pollOption.poll
|
||||
// else { return }
|
||||
// poll.update(isVoting: false)
|
||||
// }
|
||||
}
|
||||
|
||||
} // end Task
|
||||
|
@ -373,48 +363,27 @@ extension StatusTableViewCellDelegate where Self: DataSourceProvider & AuthConte
|
|||
) {
|
||||
guard let pollTableViewDiffableDataSource = statusView.pollTableViewDiffableDataSource else { return }
|
||||
guard let firstPollItem = pollTableViewDiffableDataSource.snapshot().itemIdentifiers.first else { return }
|
||||
guard case let .option(firstPollOption) = firstPollItem else { return }
|
||||
guard case let .option(option, poll) = firstPollItem else { return }
|
||||
|
||||
let managedObjectContext = context.managedObjectContext
|
||||
|
||||
Task {
|
||||
var _poll: ManagedObjectRecord<Poll>?
|
||||
var _choices: [Int]?
|
||||
|
||||
try await managedObjectContext.performChanges {
|
||||
guard let poll = firstPollOption.object(in: managedObjectContext)?.poll else { return }
|
||||
_poll = .init(objectID: poll.objectID)
|
||||
|
||||
guard poll.multiple else { return }
|
||||
|
||||
// mark voting
|
||||
poll.update(isVoting: true)
|
||||
// set choice
|
||||
_choices = poll.options
|
||||
.filter { $0.isSelected }
|
||||
.map { Int($0.index) }
|
||||
|
||||
poll.update(updatedAt: Date())
|
||||
}
|
||||
|
||||
// Trigger vote API request for
|
||||
guard let poll = _poll,
|
||||
let choices = _choices
|
||||
else { return }
|
||||
|
||||
do {
|
||||
_ = try await context.apiService.vote(
|
||||
poll: poll,
|
||||
choices: choices,
|
||||
authenticationBox: authContext.mastodonAuthenticationBox
|
||||
)
|
||||
} catch {
|
||||
// restore voting state
|
||||
try await managedObjectContext.performChanges {
|
||||
guard let poll = poll.object(in: managedObjectContext) else { return }
|
||||
poll.update(isVoting: false)
|
||||
}
|
||||
}
|
||||
assertionFailure("Re-implement this")
|
||||
// do {
|
||||
// _ = try await context.apiService.vote(
|
||||
// poll: poll,
|
||||
// choices: choices,
|
||||
// authenticationBox: authContext.mastodonAuthenticationBox
|
||||
// )
|
||||
// } catch {
|
||||
// // restore voting state
|
||||
// try await managedObjectContext.performChanges {
|
||||
// guard let poll = poll.object(in: managedObjectContext) else { return }
|
||||
// poll.update(isVoting: false)
|
||||
// }
|
||||
// }
|
||||
|
||||
} // end Task
|
||||
}
|
||||
|
@ -470,16 +439,7 @@ extension StatusTableViewCellDelegate where Self: DataSourceProvider & AuthConte
|
|||
assertionFailure("only works for status data provider")
|
||||
return
|
||||
}
|
||||
let _author: ManagedObjectRecord<MastodonUser>? = try await self.context.managedObjectContext.perform {
|
||||
guard let _status = status.object(in: self.context.managedObjectContext) else { return nil }
|
||||
let author = (_status.reblog ?? _status).author
|
||||
return .init(objectID: author.objectID)
|
||||
}
|
||||
guard let author = _author else {
|
||||
assertionFailure()
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
if case .translateStatus = action {
|
||||
DispatchQueue.main.async {
|
||||
if let cell = cell as? StatusTableViewCell {
|
||||
|
@ -513,7 +473,7 @@ extension StatusTableViewCellDelegate where Self: DataSourceProvider & AuthConte
|
|||
dependency: self,
|
||||
action: action,
|
||||
menuContext: .init(
|
||||
author: author,
|
||||
author: status.account,
|
||||
statusViewModel: statusViewModel,
|
||||
button: button,
|
||||
barButtonItem: nil
|
||||
|
@ -679,11 +639,7 @@ extension StatusTableViewCellDelegate where Self: DataSourceProvider & AuthConte
|
|||
assertionFailure("only works for status data provider")
|
||||
return
|
||||
}
|
||||
|
||||
guard let status = status.object(in: context.managedObjectContext) else {
|
||||
return await coordinator.hideLoading()
|
||||
}
|
||||
|
||||
|
||||
do {
|
||||
let edits = try await context.apiService.getHistory(forStatusID: status.id, authenticationBox: authContext.mastodonAuthenticationBox).value
|
||||
|
||||
|
|
|
@ -6,8 +6,8 @@
|
|||
//
|
||||
|
||||
import UIKit
|
||||
import CoreDataStack
|
||||
import MastodonCore
|
||||
import MastodonSDK
|
||||
|
||||
extension StatusTableViewControllerNavigateableCore where Self: DataSourceProvider & StatusTableViewControllerNavigateableRelay {
|
||||
|
||||
|
@ -55,7 +55,7 @@ extension StatusTableViewControllerNavigateableCore where Self: DataSourceProvid
|
|||
extension StatusTableViewControllerNavigateableCore where Self: DataSourceProvider & AuthContextProvider {
|
||||
|
||||
@MainActor
|
||||
private func statusRecord() async -> ManagedObjectRecord<Status>? {
|
||||
private func statusRecord() async -> Mastodon.Entity.Status? {
|
||||
guard let indexPathForSelectedRow = tableView.indexPathForSelectedRow else { return nil }
|
||||
let source = DataSourceItem.Source(indexPath: indexPathForSelectedRow)
|
||||
guard let item = await item(from: source) else { return nil }
|
||||
|
@ -64,15 +64,7 @@ extension StatusTableViewControllerNavigateableCore where Self: DataSourceProvid
|
|||
case .status(let record):
|
||||
return record
|
||||
case .notification(let record):
|
||||
let _statusRecord: ManagedObjectRecord<Status>? = try? await context.managedObjectContext.perform {
|
||||
guard let notification = record.object(in: self.context.managedObjectContext) else { return nil }
|
||||
guard let status = notification.status else { return nil }
|
||||
return .init(objectID: status.objectID)
|
||||
}
|
||||
guard let statusRecord = _statusRecord else {
|
||||
return nil
|
||||
}
|
||||
return statusRecord
|
||||
return record.status
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -40,30 +40,17 @@ extension UITableViewDelegate where Self: DataSourceProvider & AuthContextProvid
|
|||
tag: tag
|
||||
)
|
||||
case .notification(let notification):
|
||||
let managedObjectContext = context.managedObjectContext
|
||||
|
||||
let _status: ManagedObjectRecord<Status>? = try await managedObjectContext.perform {
|
||||
guard let notification = notification.object(in: managedObjectContext) else { return nil }
|
||||
guard let status = notification.status else { return nil }
|
||||
return .init(objectID: status.objectID)
|
||||
}
|
||||
if let status = _status {
|
||||
if let status = notification.status {
|
||||
await DataSourceFacade.coordinateToStatusThreadScene(
|
||||
provider: self,
|
||||
target: .status, // remove reblog wrapper
|
||||
status: status
|
||||
)
|
||||
} else {
|
||||
let _author: ManagedObjectRecord<MastodonUser>? = try await managedObjectContext.perform {
|
||||
guard let notification = notification.object(in: managedObjectContext) else { return nil }
|
||||
return .init(objectID: notification.account.objectID)
|
||||
}
|
||||
if let author = _author {
|
||||
await DataSourceFacade.coordinateToProfileScene(
|
||||
provider: self,
|
||||
user: author
|
||||
)
|
||||
}
|
||||
await DataSourceFacade.coordinateToProfileScene(
|
||||
provider: self,
|
||||
user: notification.account
|
||||
)
|
||||
}
|
||||
}
|
||||
} // end Task
|
||||
|
|
|
@ -7,22 +7,19 @@
|
|||
//
|
||||
|
||||
import UIKit
|
||||
import CoreDataStack
|
||||
import MastodonSDK
|
||||
import class CoreDataStack.Notification
|
||||
|
||||
enum DataSourceItem: Hashable {
|
||||
case status(record: ManagedObjectRecord<Status>)
|
||||
case user(record: ManagedObjectRecord<MastodonUser>)
|
||||
case status(record: Mastodon.Entity.Status)
|
||||
case user(record: Mastodon.Entity.Account)
|
||||
case hashtag(tag: TagKind)
|
||||
case notification(record: ManagedObjectRecord<Notification>)
|
||||
case notification(record: Mastodon.Entity.Notification)
|
||||
case account(account: Mastodon.Entity.Account, relationship: Mastodon.Entity.Relationship?)
|
||||
}
|
||||
|
||||
extension DataSourceItem {
|
||||
enum TagKind: Hashable {
|
||||
case entity(Mastodon.Entity.Tag)
|
||||
case record(ManagedObjectRecord<Tag>)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ final class ComposeViewModel {
|
|||
|
||||
enum Context {
|
||||
case composeStatus
|
||||
case editStatus(status: Status, statusSource: Mastodon.Entity.StatusSource)
|
||||
case editStatus(status: Mastodon.Entity.Status, statusSource: Mastodon.Entity.StatusSource)
|
||||
}
|
||||
|
||||
var disposeBag = Set<AnyCancellable>()
|
||||
|
|
|
@ -29,7 +29,7 @@ extension DiscoveryCommunityViewModel {
|
|||
|
||||
stateMachine.enter(State.Reloading.self)
|
||||
|
||||
statusFetchedResultsController.$records
|
||||
$records
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] records in
|
||||
guard let self = self else { return }
|
||||
|
|
|
@ -145,10 +145,10 @@ extension DiscoveryCommunityViewModel.State {
|
|||
self.maxID = newMaxID
|
||||
|
||||
var hasNewStatusesAppend = false
|
||||
var statusIDs = isReloading ? [] : viewModel.statusFetchedResultsController.statusIDs
|
||||
var newRecords = isReloading ? [] : viewModel.records
|
||||
for status in response.value {
|
||||
guard !statusIDs.contains(status.id) else { continue }
|
||||
statusIDs.append(status.id)
|
||||
guard !newRecords.contains(where: { $0.id == status.id }) else { continue }
|
||||
newRecords.append(status)
|
||||
hasNewStatusesAppend = true
|
||||
}
|
||||
|
||||
|
@ -158,7 +158,7 @@ extension DiscoveryCommunityViewModel.State {
|
|||
} else {
|
||||
await enter(state: NoMore.self)
|
||||
}
|
||||
viewModel.statusFetchedResultsController.statusIDs = statusIDs
|
||||
viewModel.records = newRecords
|
||||
viewModel.didLoadLatest.send()
|
||||
|
||||
} catch {
|
||||
|
|
|
@ -21,7 +21,7 @@ final class DiscoveryCommunityViewModel {
|
|||
let context: AppContext
|
||||
let authContext: AuthContext
|
||||
let viewDidAppeared = PassthroughSubject<Void, Never>()
|
||||
let statusFetchedResultsController: StatusFetchedResultsController
|
||||
@Published var records = [Mastodon.Entity.Status]()
|
||||
let listBatchFetchViewModel = ListBatchFetchViewModel()
|
||||
|
||||
// output
|
||||
|
@ -44,11 +44,5 @@ final class DiscoveryCommunityViewModel {
|
|||
init(context: AppContext, authContext: AuthContext) {
|
||||
self.context = context
|
||||
self.authContext = authContext
|
||||
self.statusFetchedResultsController = StatusFetchedResultsController(
|
||||
managedObjectContext: context.managedObjectContext,
|
||||
domain: authContext.mastodonAuthenticationBox.domain,
|
||||
additionalTweetPredicate: nil
|
||||
)
|
||||
// end init
|
||||
}
|
||||
}
|
||||
|
|
|
@ -97,11 +97,10 @@ extension DiscoveryForYouViewController: UITableViewDelegate {
|
|||
|
||||
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||
guard case let .user(record) = viewModel.diffableDataSource?.itemIdentifier(for: indexPath) else { return }
|
||||
guard let user = record.object(in: context.managedObjectContext) else { return }
|
||||
let profileViewModel = CachedProfileViewModel(
|
||||
let profileViewModel = ProfileViewModel(
|
||||
context: context,
|
||||
authContext: viewModel.authContext,
|
||||
mastodonUser: user
|
||||
optionalMastodonUser: record
|
||||
)
|
||||
_ = coordinator.present(
|
||||
scene: .profile(viewModel: profileViewModel),
|
||||
|
@ -137,9 +136,8 @@ extension DiscoveryForYouViewController: ProfileCardTableViewCellDelegate {
|
|||
) {
|
||||
guard let indexPath = tableView.indexPath(for: cell) else { return }
|
||||
guard case let .user(record) = viewModel.diffableDataSource?.itemIdentifier(for: indexPath) else { return }
|
||||
guard let user = record.object(in: context.managedObjectContext) else { return }
|
||||
|
||||
let userID = user.id
|
||||
let userID = record.id
|
||||
let _familiarFollowers = viewModel.familiarFollowers.first(where: { $0.id == userID })
|
||||
guard let familiarFollowers = _familiarFollowers else {
|
||||
assertionFailure()
|
||||
|
|
|
@ -29,7 +29,7 @@ extension DiscoveryForYouViewModel {
|
|||
try await fetch()
|
||||
}
|
||||
|
||||
userFetchedResultsController.$records
|
||||
$records
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] records in
|
||||
guard let self = self else { return }
|
||||
|
|
|
@ -20,12 +20,13 @@ final class DiscoveryForYouViewModel {
|
|||
// input
|
||||
let context: AppContext
|
||||
let authContext: AuthContext
|
||||
let userFetchedResultsController: UserFetchedResultsController
|
||||
// let userFetchedResultsController: UserFetchedResultsController
|
||||
|
||||
@MainActor
|
||||
@Published var familiarFollowers: [Mastodon.Entity.FamiliarFollowers] = []
|
||||
@Published var isFetching = false
|
||||
|
||||
@Published var records = [Mastodon.Entity.Account]()
|
||||
|
||||
// output
|
||||
var diffableDataSource: UITableViewDiffableDataSource<DiscoverySection, DiscoveryItem>?
|
||||
let didLoadLatest = PassthroughSubject<Void, Never>()
|
||||
|
@ -33,11 +34,11 @@ final class DiscoveryForYouViewModel {
|
|||
init(context: AppContext, authContext: AuthContext) {
|
||||
self.context = context
|
||||
self.authContext = authContext
|
||||
self.userFetchedResultsController = UserFetchedResultsController(
|
||||
managedObjectContext: context.managedObjectContext,
|
||||
domain: authContext.mastodonAuthenticationBox.domain,
|
||||
additionalPredicate: nil
|
||||
)
|
||||
// self.userFetchedResultsController = UserFetchedResultsController(
|
||||
// managedObjectContext: context.managedObjectContext,
|
||||
// domain: authContext.mastodonAuthenticationBox.domain,
|
||||
// additionalPredicate: nil
|
||||
// )
|
||||
// end init
|
||||
}
|
||||
}
|
||||
|
@ -58,7 +59,7 @@ extension DiscoveryForYouViewModel {
|
|||
authenticationBox: authContext.mastodonAuthenticationBox
|
||||
)
|
||||
familiarFollowers = _familiarFollowersResponse?.value ?? []
|
||||
userFetchedResultsController.userIDs = userIDs
|
||||
// userFetchedResultsController.userIDs = userIDs
|
||||
} catch {
|
||||
// do nothing
|
||||
}
|
||||
|
|
|
@ -29,7 +29,7 @@ extension DiscoveryPostsViewModel {
|
|||
|
||||
stateMachine.enter(State.Reloading.self)
|
||||
|
||||
statusFetchedResultsController.$records
|
||||
$records
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] records in
|
||||
guard let self = self else { return }
|
||||
|
|
|
@ -143,10 +143,10 @@ extension DiscoveryPostsViewModel.State {
|
|||
self.offset = newOffset
|
||||
|
||||
var hasNewStatusesAppend = false
|
||||
var statusIDs = isReloading ? [] : viewModel.statusFetchedResultsController.statusIDs
|
||||
var newStatuses = isReloading ? [] : viewModel.records
|
||||
for status in response.value {
|
||||
guard !statusIDs.contains(status.id) else { continue }
|
||||
statusIDs.append(status.id)
|
||||
guard !newStatuses.contains(where: { $0.id == status.id }) else { continue }
|
||||
newStatuses.append(status)
|
||||
hasNewStatusesAppend = true
|
||||
}
|
||||
|
||||
|
@ -155,7 +155,7 @@ extension DiscoveryPostsViewModel.State {
|
|||
} else {
|
||||
await enter(state: NoMore.self)
|
||||
}
|
||||
viewModel.statusFetchedResultsController.statusIDs = statusIDs
|
||||
viewModel.records = newStatuses
|
||||
viewModel.didLoadLatest.send()
|
||||
|
||||
} catch {
|
||||
|
|
|
@ -20,7 +20,7 @@ final class DiscoveryPostsViewModel {
|
|||
// input
|
||||
let context: AppContext
|
||||
let authContext: AuthContext
|
||||
let statusFetchedResultsController: StatusFetchedResultsController
|
||||
@Published var records = [Mastodon.Entity.Status]()
|
||||
let listBatchFetchViewModel = ListBatchFetchViewModel()
|
||||
|
||||
// output
|
||||
|
@ -44,12 +44,6 @@ final class DiscoveryPostsViewModel {
|
|||
init(context: AppContext, authContext: AuthContext) {
|
||||
self.context = context
|
||||
self.authContext = authContext
|
||||
self.statusFetchedResultsController = StatusFetchedResultsController(
|
||||
managedObjectContext: context.managedObjectContext,
|
||||
domain: authContext.mastodonAuthenticationBox.domain,
|
||||
additionalTweetPredicate: nil
|
||||
)
|
||||
// end init
|
||||
|
||||
Task {
|
||||
await checkServerEndpoint()
|
||||
|
|
|
@ -34,7 +34,7 @@ extension HashtagTimelineViewModel {
|
|||
snapshot.appendSections([.main])
|
||||
diffableDataSource?.apply(snapshot)
|
||||
|
||||
fetchedResultsController.$records
|
||||
$records
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] records in
|
||||
guard let self = self else { return }
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
import Foundation
|
||||
import GameplayKit
|
||||
import CoreDataStack
|
||||
import MastodonSDK
|
||||
|
||||
extension HashtagTimelineViewModel {
|
||||
class State: GKState {
|
||||
|
@ -93,7 +93,7 @@ extension HashtagTimelineViewModel.State {
|
|||
}
|
||||
|
||||
class Loading: HashtagTimelineViewModel.State {
|
||||
var maxID: Status.ID?
|
||||
var maxID: Mastodon.Entity.Status.ID?
|
||||
|
||||
override func isValidNextState(_ stateClass: AnyClass) -> Bool {
|
||||
switch stateClass {
|
||||
|
@ -145,10 +145,10 @@ extension HashtagTimelineViewModel.State {
|
|||
self.maxID = newMaxID
|
||||
|
||||
var hasNewStatusesAppend = false
|
||||
var statusIDs = isReloading ? [] : viewModel.fetchedResultsController.statusIDs
|
||||
var newRecords = isReloading ? [] : viewModel.records
|
||||
for status in response.value {
|
||||
guard !statusIDs.contains(status.id) else { continue }
|
||||
statusIDs.append(status.id)
|
||||
guard !newRecords.contains(where: { $0.id == status.id }) else { continue }
|
||||
newRecords.append(status)
|
||||
hasNewStatusesAppend = true
|
||||
}
|
||||
|
||||
|
@ -158,7 +158,7 @@ extension HashtagTimelineViewModel.State {
|
|||
await enter(state: NoMore.self)
|
||||
}
|
||||
|
||||
viewModel.fetchedResultsController.append(statusIDs: statusIDs)
|
||||
viewModel.records = newRecords
|
||||
viewModel.didLoadLatest.send()
|
||||
} catch {
|
||||
await enter(state: Fail.self)
|
||||
|
|
|
@ -7,8 +7,6 @@
|
|||
|
||||
import UIKit
|
||||
import Combine
|
||||
import CoreData
|
||||
import CoreDataStack
|
||||
import GameplayKit
|
||||
import MastodonSDK
|
||||
import MastodonCore
|
||||
|
@ -24,7 +22,7 @@ final class HashtagTimelineViewModel {
|
|||
// input
|
||||
let context: AppContext
|
||||
let authContext: AuthContext
|
||||
let fetchedResultsController: StatusFetchedResultsController
|
||||
// let fetchedResultsController: StatusFetchedResultsController
|
||||
let isFetchingLatestTimeline = CurrentValueSubject<Bool, Never>(false)
|
||||
let timelinePredicate = CurrentValueSubject<NSPredicate?, Never>(nil)
|
||||
let hashtagEntity = CurrentValueSubject<Mastodon.Entity.Tag?, Never>(nil)
|
||||
|
@ -34,6 +32,8 @@ final class HashtagTimelineViewModel {
|
|||
var diffableDataSource: UITableViewDiffableDataSource<StatusSection, StatusItem>?
|
||||
let didLoadLatest = PassthroughSubject<Void, Never>()
|
||||
let hashtagDetails = CurrentValueSubject<Mastodon.Entity.Tag?, Never>(nil)
|
||||
|
||||
@Published var records = [Mastodon.Entity.Status]()
|
||||
|
||||
// bottom loader
|
||||
private(set) lazy var stateMachine: GKStateMachine = {
|
||||
|
@ -54,28 +54,30 @@ final class HashtagTimelineViewModel {
|
|||
self.context = context
|
||||
self.authContext = authContext
|
||||
self.hashtag = hashtag
|
||||
self.fetchedResultsController = StatusFetchedResultsController(
|
||||
managedObjectContext: context.managedObjectContext,
|
||||
domain: authContext.mastodonAuthenticationBox.domain,
|
||||
additionalTweetPredicate: nil
|
||||
)
|
||||
// self.fetchedResultsController = StatusFetchedResultsController(
|
||||
// managedObjectContext: context.managedObjectContext,
|
||||
// domain: authContext.mastodonAuthenticationBox.domain,
|
||||
// additionalTweetPredicate: nil
|
||||
// )
|
||||
updateTagInformation()
|
||||
// end init
|
||||
}
|
||||
|
||||
func viewWillAppear() {
|
||||
let predicate = Tag.predicate(
|
||||
domain: authContext.mastodonAuthenticationBox.domain,
|
||||
name: hashtag
|
||||
)
|
||||
// let predicate = Tag.predicate(
|
||||
// domain: authContext.mastodonAuthenticationBox.domain,
|
||||
// name: hashtag
|
||||
// )
|
||||
|
||||
#warning("Re-Implement this")
|
||||
|
||||
guard
|
||||
let object = Tag.findOrFetch(in: context.managedObjectContext, matching: predicate)
|
||||
false//let object = Tag.findOrFetch(in: context.managedObjectContext, matching: predicate)
|
||||
else {
|
||||
return hashtagDetails.send(hashtagDetails.value?.copy(following: false))
|
||||
}
|
||||
|
||||
hashtagDetails.send(hashtagDetails.value?.copy(following: object.following))
|
||||
// hashtagDetails.send(hashtagDetails.value?.copy(following: object.following))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -20,17 +20,11 @@ extension HomeTimelineViewController: DataSourceProvider {
|
|||
}
|
||||
|
||||
switch item {
|
||||
case .feed(let record):
|
||||
let managedObjectContext = context.managedObjectContext
|
||||
let item: DataSourceItem? = try? await managedObjectContext.perform {
|
||||
guard let feed = record.object(in: managedObjectContext) else { return nil }
|
||||
guard feed.kind == .home else { return nil }
|
||||
if let status = feed.status {
|
||||
return .status(record: .init(objectID: status.objectID))
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
case .feed(let record):
|
||||
let item: DataSourceItem? = {
|
||||
guard let status = record.status else { return nil }
|
||||
return .status(record: status)
|
||||
}()
|
||||
return item
|
||||
default:
|
||||
return nil
|
||||
|
|
|
@ -9,6 +9,7 @@ import UIKit
|
|||
import CoreData
|
||||
import CoreDataStack
|
||||
import MastodonUI
|
||||
import MastodonSDK
|
||||
|
||||
extension HomeTimelineViewModel {
|
||||
|
||||
|
@ -35,7 +36,7 @@ extension HomeTimelineViewModel {
|
|||
snapshot.appendSections([.main])
|
||||
diffableDataSource?.apply(snapshot)
|
||||
|
||||
fetchedResultsController.$records
|
||||
$records
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] records in
|
||||
guard let self = self else { return }
|
||||
|
@ -45,7 +46,7 @@ extension HomeTimelineViewModel {
|
|||
let oldSnapshot = diffableDataSource.snapshot()
|
||||
var newSnapshot: NSDiffableDataSourceSnapshot<StatusSection, StatusItem> = {
|
||||
let newItems = records.map { record in
|
||||
StatusItem.feed(record: record)
|
||||
StatusItem.feed(record: .init(status: record, notification: nil, hasMore: false, isLoadingMore: false))
|
||||
}
|
||||
var snapshot = NSDiffableDataSourceSnapshot<StatusSection, StatusItem>()
|
||||
snapshot.appendSections([.main])
|
||||
|
@ -53,36 +54,37 @@ extension HomeTimelineViewModel {
|
|||
return snapshot
|
||||
}()
|
||||
|
||||
let parentManagedObjectContext = self.context.managedObjectContext
|
||||
let managedObjectContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
|
||||
managedObjectContext.parent = parentManagedObjectContext
|
||||
try? await managedObjectContext.perform {
|
||||
let anchors: [Feed] = {
|
||||
let request = Feed.sortedFetchRequest
|
||||
request.predicate = NSCompoundPredicate(andPredicateWithSubpredicates: [
|
||||
Feed.hasMorePredicate(),
|
||||
self.fetchedResultsController.predicate,
|
||||
])
|
||||
do {
|
||||
return try managedObjectContext.fetch(request)
|
||||
} catch {
|
||||
assertionFailure(error.localizedDescription)
|
||||
return []
|
||||
}
|
||||
}()
|
||||
|
||||
let itemIdentifiers = newSnapshot.itemIdentifiers
|
||||
for (index, item) in itemIdentifiers.enumerated() {
|
||||
guard case let .feed(record) = item else { continue }
|
||||
guard anchors.contains(where: { feed in feed.objectID == record.objectID }) else { continue }
|
||||
let isLast = index + 1 == itemIdentifiers.count
|
||||
if isLast {
|
||||
newSnapshot.insertItems([.bottomLoader], afterItem: item)
|
||||
} else {
|
||||
newSnapshot.insertItems([.feedLoader(record: record)], afterItem: item)
|
||||
}
|
||||
}
|
||||
}
|
||||
#warning("Code below needs to be re-implemented")
|
||||
// let parentManagedObjectContext = self.context.managedObjectContext
|
||||
// let managedObjectContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
|
||||
// managedObjectContext.parent = parentManagedObjectContext
|
||||
// try? await managedObjectContext.perform {
|
||||
// let anchors: [Feed] = {
|
||||
// let request = Feed.sortedFetchRequest
|
||||
// request.predicate = NSCompoundPredicate(andPredicateWithSubpredicates: [
|
||||
// Feed.hasMorePredicate(),
|
||||
// self.fetchedResultsController.predicate,
|
||||
// ])
|
||||
// do {
|
||||
// return try managedObjectContext.fetch(request)
|
||||
// } catch {
|
||||
// assertionFailure(error.localizedDescription)
|
||||
// return []
|
||||
// }
|
||||
// }()
|
||||
//
|
||||
// let itemIdentifiers = newSnapshot.itemIdentifiers
|
||||
// for (index, item) in itemIdentifiers.enumerated() {
|
||||
// guard case let .feed(record) = item else { continue }
|
||||
// guard anchors.contains(where: { feed in feed.objectID == record.objectID }) else { continue }
|
||||
// let isLast = index + 1 == itemIdentifiers.count
|
||||
// if isLast {
|
||||
// newSnapshot.insertItems([.bottomLoader], afterItem: item)
|
||||
// } else {
|
||||
// newSnapshot.insertItems([.feedLoader(record: record)], afterItem: item)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
let hasChanges = newSnapshot.itemIdentifiers != oldSnapshot.itemIdentifiers
|
||||
if !hasChanges && !self.hasPendingStatusEditReload {
|
||||
|
|
|
@ -7,10 +7,9 @@
|
|||
|
||||
import func QuartzCore.CACurrentMediaTime
|
||||
import Foundation
|
||||
import CoreData
|
||||
import CoreDataStack
|
||||
import GameplayKit
|
||||
import MastodonCore
|
||||
import MastodonSDK
|
||||
|
||||
extension HomeTimelineViewModel {
|
||||
class LoadLatestState: GKState {
|
||||
|
@ -83,15 +82,11 @@ extension HomeTimelineViewModel.LoadLatestState {
|
|||
|
||||
guard let viewModel else { return }
|
||||
|
||||
let latestFeedRecords = viewModel.fetchedResultsController.records.prefix(APIService.onceRequestStatusMaxCount)
|
||||
let parentManagedObjectContext = viewModel.fetchedResultsController.managedObjectContext
|
||||
let managedObjectContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
|
||||
managedObjectContext.parent = parentManagedObjectContext
|
||||
let latestFeedRecords = viewModel.records.prefix(APIService.onceRequestStatusMaxCount)
|
||||
|
||||
Task {
|
||||
let latestStatusIDs: [Status.ID] = latestFeedRecords.compactMap { record in
|
||||
guard let feed = record.object(in: managedObjectContext) else { return nil }
|
||||
return feed.status?.id
|
||||
let latestStatusIDs: [Mastodon.Entity.Status.ID] = latestFeedRecords.compactMap { record in
|
||||
return record.id
|
||||
}
|
||||
|
||||
do {
|
||||
|
|
|
@ -31,7 +31,7 @@ extension HomeTimelineViewModel.LoadOldestState {
|
|||
class Initial: HomeTimelineViewModel.LoadOldestState {
|
||||
override func isValidNextState(_ stateClass: AnyClass) -> Bool {
|
||||
guard let viewModel = viewModel else { return false }
|
||||
guard !viewModel.fetchedResultsController.records.isEmpty else { return false }
|
||||
guard !viewModel.records.isEmpty else { return false }
|
||||
return stateClass == Loading.self
|
||||
}
|
||||
}
|
||||
|
@ -46,19 +46,13 @@ extension HomeTimelineViewModel.LoadOldestState {
|
|||
|
||||
guard let viewModel = viewModel, let stateMachine = stateMachine else { return }
|
||||
|
||||
guard let lastFeedRecord = viewModel.fetchedResultsController.records.last else {
|
||||
guard let lastFeedRecord = viewModel.records.last else {
|
||||
stateMachine.enter(Idle.self)
|
||||
return
|
||||
}
|
||||
|
||||
Task {
|
||||
let managedObjectContext = viewModel.fetchedResultsController.managedObjectContext
|
||||
let _maxID: Mastodon.Entity.Status.ID? = try await managedObjectContext.perform {
|
||||
guard let feed = lastFeedRecord.object(in: managedObjectContext),
|
||||
let status = feed.status
|
||||
else { return nil }
|
||||
return status.id
|
||||
}
|
||||
let _maxID: Mastodon.Entity.Status.ID? = viewModel.records.last?.id
|
||||
|
||||
guard let maxID = _maxID else {
|
||||
await self.enter(state: Fail.self)
|
||||
|
|
|
@ -9,12 +9,11 @@ import func AVFoundation.AVMakeRect
|
|||
import UIKit
|
||||
import AVKit
|
||||
import Combine
|
||||
import CoreData
|
||||
import CoreDataStack
|
||||
import GameplayKit
|
||||
import AlamofireImage
|
||||
import MastodonCore
|
||||
import MastodonUI
|
||||
import MastodonSDK
|
||||
|
||||
final class HomeTimelineViewModel: NSObject {
|
||||
|
||||
|
@ -24,7 +23,7 @@ final class HomeTimelineViewModel: NSObject {
|
|||
// input
|
||||
let context: AppContext
|
||||
let authContext: AuthContext
|
||||
let fetchedResultsController: FeedFetchedResultsController
|
||||
// let fetchedResultsController: FeedFetchedResultsController
|
||||
let homeTimelineNavigationBarTitleViewModel: HomeTimelineNavigationBarTitleViewModel
|
||||
let listBatchFetchViewModel = ListBatchFetchViewModel()
|
||||
|
||||
|
@ -34,6 +33,7 @@ final class HomeTimelineViewModel: NSObject {
|
|||
@Published var scrollPositionRecord: ScrollPositionRecord? = nil
|
||||
@Published var displaySettingBarButtonItem = true
|
||||
@Published var hasPendingStatusEditReload = false
|
||||
@Published var records = [Mastodon.Entity.Status]()
|
||||
|
||||
weak var tableView: UITableView?
|
||||
weak var timelineMiddleLoaderTableViewCellDelegate: TimelineMiddleLoaderTableViewCellDelegate?
|
||||
|
@ -80,14 +80,19 @@ final class HomeTimelineViewModel: NSObject {
|
|||
init(context: AppContext, authContext: AuthContext) {
|
||||
self.context = context
|
||||
self.authContext = authContext
|
||||
self.fetchedResultsController = FeedFetchedResultsController(managedObjectContext: context.managedObjectContext)
|
||||
// self.fetchedResultsController = FeedFetchedResultsController(managedObjectContext: context.managedObjectContext)
|
||||
self.homeTimelineNavigationBarTitleViewModel = HomeTimelineNavigationBarTitleViewModel(context: context)
|
||||
super.init()
|
||||
|
||||
fetchedResultsController.predicate = Feed.predicate(
|
||||
kind: .home,
|
||||
acct: .mastodon(domain: authContext.mastodonAuthenticationBox.domain, userID: authContext.mastodonAuthenticationBox.userID)
|
||||
)
|
||||
// fetchedResultsController.predicate = Feed.predicate(
|
||||
// kind: .home,
|
||||
// acct: .mastodon(domain: authContext.mastodonAuthenticationBox.domain, userID: authContext.mastodonAuthenticationBox.userID)
|
||||
// )
|
||||
|
||||
Task {
|
||||
records = try await context.apiService.homeTimeline(authenticationBox: authContext.mastodonAuthenticationBox)
|
||||
.value
|
||||
}
|
||||
|
||||
homeTimelineNeedRefresh
|
||||
.sink { [weak self] _ in
|
||||
|
@ -116,7 +121,15 @@ extension HomeTimelineViewModel {
|
|||
|
||||
extension HomeTimelineViewModel {
|
||||
func timelineDidReachEnd() {
|
||||
fetchedResultsController.fetchNextBatch()
|
||||
// fetchedResultsController.fetchNextBatch()
|
||||
Task {
|
||||
let newRecords = try await context.apiService.homeTimeline(
|
||||
sinceID: records.last?.id,
|
||||
authenticationBox: authContext.mastodonAuthenticationBox
|
||||
).value
|
||||
|
||||
records += newRecords
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -124,29 +137,29 @@ extension HomeTimelineViewModel {
|
|||
|
||||
// load timeline gap
|
||||
func loadMore(item: StatusItem) async {
|
||||
guard case let .feedLoader(record) = item else { return }
|
||||
guard case let .feedLoader(record) = item, let status = record.status else { return }
|
||||
guard let diffableDataSource = diffableDataSource else { return }
|
||||
var snapshot = diffableDataSource.snapshot()
|
||||
|
||||
let managedObjectContext = context.managedObjectContext
|
||||
let key = "LoadMore@\(record.objectID)"
|
||||
|
||||
guard let feed = record.object(in: managedObjectContext) else { return }
|
||||
guard let status = feed.status else { return }
|
||||
// let managedObjectContext = context.managedObjectContext
|
||||
let key = "LoadMore@\(status.id)"
|
||||
|
||||
// keep transient property live
|
||||
managedObjectContext.cache(feed, key: key)
|
||||
defer {
|
||||
managedObjectContext.cache(nil, key: key)
|
||||
}
|
||||
do {
|
||||
// update state
|
||||
try await managedObjectContext.performChanges {
|
||||
feed.update(isLoadingMore: true)
|
||||
}
|
||||
} catch {
|
||||
assertionFailure(error.localizedDescription)
|
||||
}
|
||||
// guard let feed = record.object(in: managedObjectContext) else { return }
|
||||
// guard let status = feed.status else { return }
|
||||
|
||||
// keep transient property live
|
||||
// managedObjectContext.cache(feed, key: key)
|
||||
// defer {
|
||||
// managedObjectContext.cache(nil, key: key)
|
||||
// }
|
||||
// do {
|
||||
// // update state
|
||||
// try await managedObjectContext.performChanges {
|
||||
// feed.update(isLoadingMore: true)
|
||||
// }
|
||||
// } catch {
|
||||
// assertionFailure(error.localizedDescription)
|
||||
// }
|
||||
|
||||
// reconfigure item
|
||||
snapshot.reconfigureItems([item])
|
||||
|
@ -160,14 +173,14 @@ extension HomeTimelineViewModel {
|
|||
authenticationBox: authContext.mastodonAuthenticationBox
|
||||
)
|
||||
} catch {
|
||||
do {
|
||||
// restore state
|
||||
try await managedObjectContext.performChanges {
|
||||
feed.update(isLoadingMore: false)
|
||||
}
|
||||
} catch {
|
||||
assertionFailure(error.localizedDescription)
|
||||
}
|
||||
// do {
|
||||
// // restore state
|
||||
// try await managedObjectContext.performChanges {
|
||||
// feed.update(isLoadingMore: false)
|
||||
// }
|
||||
// } catch {
|
||||
// assertionFailure(error.localizedDescription)
|
||||
// }
|
||||
}
|
||||
|
||||
// reconfigure item again
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
import UIKit
|
||||
import Combine
|
||||
import CoreDataStack
|
||||
import MastodonSDK
|
||||
|
||||
extension NotificationTableViewCell {
|
||||
final class ViewModel {
|
||||
|
@ -18,7 +18,7 @@ extension NotificationTableViewCell {
|
|||
}
|
||||
|
||||
enum Value {
|
||||
case feed(Feed)
|
||||
case feed(FeedItem)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,16 +21,13 @@ extension NotificationTimelineViewController: DataSourceProvider {
|
|||
|
||||
switch item {
|
||||
case .feed(let record):
|
||||
let managedObjectContext = context.managedObjectContext
|
||||
let item: DataSourceItem? = try? await managedObjectContext.perform {
|
||||
guard let feed = record.object(in: managedObjectContext) else { return nil }
|
||||
guard feed.kind == .notificationAll || feed.kind == .notificationMentions else { return nil }
|
||||
if let notification = feed.notification {
|
||||
return .notification(record: .init(objectID: notification.objectID))
|
||||
let item: DataSourceItem? = {
|
||||
if let notification = record.notification {
|
||||
return .notification(record: notification)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}()
|
||||
return item
|
||||
default:
|
||||
return nil
|
||||
|
|
|
@ -280,14 +280,13 @@ extension NotificationTimelineViewController: TableViewControllerNavigateable {
|
|||
Task { @MainActor in
|
||||
switch item {
|
||||
case .feed(let record):
|
||||
guard let feed = record.object(in: self.context.managedObjectContext) else { return }
|
||||
guard let notification = feed.notification else { return }
|
||||
guard let notification = record.notification else { return }
|
||||
|
||||
if let stauts = notification.status {
|
||||
if let status = notification.status {
|
||||
let threadViewModel = ThreadViewModel(
|
||||
context: self.context,
|
||||
authContext: self.viewModel.authContext,
|
||||
optionalRoot: .root(context: .init(status: .init(objectID: stauts.objectID)))
|
||||
optionalRoot: .root(context: .init(status: status))
|
||||
)
|
||||
_ = self.coordinator.present(
|
||||
scene: .thread(viewModel: threadViewModel),
|
||||
|
|
|
@ -30,7 +30,7 @@ extension NotificationTimelineViewModel {
|
|||
snapshot.appendSections([.main])
|
||||
diffableDataSource?.apply(snapshot)
|
||||
|
||||
feedFetchedResultsController.$records
|
||||
$records
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] records in
|
||||
guard let self = self else { return }
|
||||
|
@ -40,7 +40,7 @@ extension NotificationTimelineViewModel {
|
|||
let oldSnapshot = diffableDataSource.snapshot()
|
||||
var newSnapshot: NSDiffableDataSourceSnapshot<NotificationSection, NotificationItem> = {
|
||||
let newItems = records.map { record in
|
||||
NotificationItem.feed(record: record)
|
||||
NotificationItem.feed(record: .init(status: nil, notification: record, hasMore: false, isLoadingMore: false))
|
||||
}
|
||||
var snapshot = NSDiffableDataSourceSnapshot<NotificationSection, NotificationItem>()
|
||||
snapshot.appendSections([.main])
|
||||
|
@ -48,36 +48,37 @@ extension NotificationTimelineViewModel {
|
|||
return snapshot
|
||||
}()
|
||||
|
||||
let parentManagedObjectContext = self.context.managedObjectContext
|
||||
let managedObjectContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
|
||||
managedObjectContext.parent = parentManagedObjectContext
|
||||
try? await managedObjectContext.perform {
|
||||
let anchors: [Feed] = {
|
||||
let request = Feed.sortedFetchRequest
|
||||
request.predicate = NSCompoundPredicate(andPredicateWithSubpredicates: [
|
||||
Feed.hasMorePredicate(),
|
||||
self.feedFetchedResultsController.predicate,
|
||||
])
|
||||
do {
|
||||
return try managedObjectContext.fetch(request)
|
||||
} catch {
|
||||
assertionFailure(error.localizedDescription)
|
||||
return []
|
||||
}
|
||||
}()
|
||||
|
||||
let itemIdentifiers = newSnapshot.itemIdentifiers
|
||||
for (index, item) in itemIdentifiers.enumerated() {
|
||||
guard case let .feed(record) = item else { continue }
|
||||
guard anchors.contains(where: { feed in feed.objectID == record.objectID }) else { continue }
|
||||
let isLast = index + 1 == itemIdentifiers.count
|
||||
if isLast {
|
||||
newSnapshot.insertItems([.bottomLoader], afterItem: item)
|
||||
} else {
|
||||
newSnapshot.insertItems([.feedLoader(record: record)], afterItem: item)
|
||||
}
|
||||
}
|
||||
}
|
||||
#warning("Code below needs to be re-implemented")
|
||||
// let parentManagedObjectContext = self.context.managedObjectContext
|
||||
// let managedObjectContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
|
||||
// managedObjectContext.parent = parentManagedObjectContext
|
||||
// try? await managedObjectContext.perform {
|
||||
// let anchors: [Feed] = {
|
||||
// let request = Feed.sortedFetchRequest
|
||||
// request.predicate = NSCompoundPredicate(andPredicateWithSubpredicates: [
|
||||
// Feed.hasMorePredicate(),
|
||||
// self.feedFetchedResultsController.predicate,
|
||||
// ])
|
||||
// do {
|
||||
// return try managedObjectContext.fetch(request)
|
||||
// } catch {
|
||||
// assertionFailure(error.localizedDescription)
|
||||
// return []
|
||||
// }
|
||||
// }()
|
||||
//
|
||||
// let itemIdentifiers = newSnapshot.itemIdentifiers
|
||||
// for (index, item) in itemIdentifiers.enumerated() {
|
||||
// guard case let .feed(record) = item else { continue }
|
||||
// guard anchors.contains(where: { feed in feed.objectID == record.objectID }) else { continue }
|
||||
// let isLast = index + 1 == itemIdentifiers.count
|
||||
// if isLast {
|
||||
// newSnapshot.insertItems([.bottomLoader], afterItem: item)
|
||||
// } else {
|
||||
// newSnapshot.insertItems([.feedLoader(record: record)], afterItem: item)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
let hasChanges = newSnapshot.itemIdentifiers != oldSnapshot.itemIdentifiers
|
||||
if !hasChanges {
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
// Created by MainasuK on 2022-1-21.
|
||||
//
|
||||
|
||||
import CoreDataStack
|
||||
import Foundation
|
||||
import GameplayKit
|
||||
import MastodonSDK
|
||||
|
@ -32,7 +31,7 @@ extension NotificationTimelineViewModel.LoadOldestState {
|
|||
class Initial: NotificationTimelineViewModel.LoadOldestState {
|
||||
override func isValidNextState(_ stateClass: AnyClass) -> Bool {
|
||||
guard let viewModel = viewModel else { return false }
|
||||
guard !viewModel.feedFetchedResultsController.records.isEmpty else { return false }
|
||||
guard !viewModel.records.isEmpty else { return false }
|
||||
return stateClass == Loading.self
|
||||
}
|
||||
}
|
||||
|
@ -47,7 +46,7 @@ extension NotificationTimelineViewModel.LoadOldestState {
|
|||
|
||||
guard let viewModel = viewModel, let stateMachine = stateMachine else { return }
|
||||
|
||||
guard let lastFeedRecord = viewModel.feedFetchedResultsController.records.last else {
|
||||
guard let lastFeedRecord = viewModel.records.last else {
|
||||
stateMachine.enter(Fail.self)
|
||||
return
|
||||
}
|
||||
|
@ -55,12 +54,7 @@ extension NotificationTimelineViewModel.LoadOldestState {
|
|||
|
||||
Task {
|
||||
let managedObjectContext = viewModel.context.managedObjectContext
|
||||
let _maxID: Mastodon.Entity.Notification.ID? = try await managedObjectContext.perform {
|
||||
guard let feed = lastFeedRecord.object(in: managedObjectContext),
|
||||
let notification = feed.notification
|
||||
else { return nil }
|
||||
return notification.id
|
||||
}
|
||||
let _maxID: Mastodon.Entity.Notification.ID? = viewModel.records.last?.id
|
||||
|
||||
guard let maxID = _maxID else {
|
||||
await self.enter(state: Fail.self)
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
|
||||
import UIKit
|
||||
import Combine
|
||||
import CoreDataStack
|
||||
import GameplayKit
|
||||
import MastodonSDK
|
||||
import MastodonCore
|
||||
|
@ -20,10 +19,11 @@ final class NotificationTimelineViewModel {
|
|||
let context: AppContext
|
||||
let authContext: AuthContext
|
||||
let scope: Scope
|
||||
let feedFetchedResultsController: FeedFetchedResultsController
|
||||
// let feedFetchedResultsController: FeedFetchedResultsController
|
||||
let listBatchFetchViewModel = ListBatchFetchViewModel()
|
||||
@Published var isLoadingLatest = false
|
||||
@Published var lastAutomaticFetchTimestamp: Date?
|
||||
@Published var records = [Mastodon.Entity.Notification]()
|
||||
|
||||
// output
|
||||
var diffableDataSource: UITableViewDiffableDataSource<NotificationSection, NotificationItem>?
|
||||
|
@ -51,10 +51,15 @@ final class NotificationTimelineViewModel {
|
|||
self.context = context
|
||||
self.authContext = authContext
|
||||
self.scope = scope
|
||||
self.feedFetchedResultsController = FeedFetchedResultsController(managedObjectContext: context.managedObjectContext)
|
||||
// self.feedFetchedResultsController = FeedFetchedResultsController(managedObjectContext: context.managedObjectContext)
|
||||
// end init
|
||||
|
||||
feedFetchedResultsController.predicate = NotificationTimelineViewModel.feedPredicate(
|
||||
// feedFetchedResultsController.predicate = NotificationTimelineViewModel.feedPredicate(
|
||||
// authenticationBox: authContext.mastodonAuthenticationBox,
|
||||
// scope: scope
|
||||
// )
|
||||
|
||||
loadNotifications(
|
||||
authenticationBox: authContext.mastodonAuthenticationBox,
|
||||
scope: scope
|
||||
)
|
||||
|
@ -67,40 +72,55 @@ extension NotificationTimelineViewModel {
|
|||
|
||||
typealias Scope = APIService.MastodonNotificationScope
|
||||
|
||||
static func feedPredicate(
|
||||
func loadNotifications(
|
||||
authenticationBox: MastodonAuthenticationBox,
|
||||
scope: Scope
|
||||
) -> NSPredicate {
|
||||
let domain = authenticationBox.domain
|
||||
let userID = authenticationBox.userID
|
||||
let acct = Feed.Acct.mastodon(
|
||||
domain: domain,
|
||||
userID: userID
|
||||
)
|
||||
|
||||
let predicate: NSPredicate = {
|
||||
switch scope {
|
||||
case .everything:
|
||||
return NSCompoundPredicate(andPredicateWithSubpredicates: [
|
||||
Feed.hasNotificationPredicate(),
|
||||
Feed.predicate(
|
||||
kind: .notificationAll,
|
||||
acct: acct
|
||||
)
|
||||
])
|
||||
case .mentions:
|
||||
return NSCompoundPredicate(andPredicateWithSubpredicates: [
|
||||
Feed.hasNotificationPredicate(),
|
||||
Feed.predicate(
|
||||
kind: .notificationMentions,
|
||||
acct: acct
|
||||
),
|
||||
Feed.notificationTypePredicate(types: scope.includeTypes ?? [])
|
||||
])
|
||||
}
|
||||
}()
|
||||
return predicate
|
||||
) {
|
||||
Task {
|
||||
let notifications = try await context.apiService.notifications(
|
||||
maxID: nil,
|
||||
scope: scope,
|
||||
authenticationBox: authenticationBox
|
||||
).value
|
||||
|
||||
records = notifications
|
||||
}
|
||||
}
|
||||
|
||||
// static func feedPredicate(
|
||||
// authenticationBox: MastodonAuthenticationBox,
|
||||
// scope: Scope
|
||||
// ) -> NSPredicate {
|
||||
// let domain = authenticationBox.domain
|
||||
// let userID = authenticationBox.userID
|
||||
// let acct = Feed.Acct.mastodon(
|
||||
// domain: domain,
|
||||
// userID: userID
|
||||
// )
|
||||
//
|
||||
// let predicate: NSPredicate = {
|
||||
// switch scope {
|
||||
// case .everything:
|
||||
// return NSCompoundPredicate(andPredicateWithSubpredicates: [
|
||||
// Feed.hasNotificationPredicate(),
|
||||
// Feed.predicate(
|
||||
// kind: .notificationAll,
|
||||
// acct: acct
|
||||
// )
|
||||
// ])
|
||||
// case .mentions:
|
||||
// return NSCompoundPredicate(andPredicateWithSubpredicates: [
|
||||
// Feed.hasNotificationPredicate(),
|
||||
// Feed.predicate(
|
||||
// kind: .notificationMentions,
|
||||
// acct: acct
|
||||
// ),
|
||||
// Feed.notificationTypePredicate(types: scope.includeTypes ?? [])
|
||||
// ])
|
||||
// }
|
||||
// }()
|
||||
// return predicate
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
|
@ -124,26 +144,26 @@ extension NotificationTimelineViewModel {
|
|||
|
||||
// load timeline gap
|
||||
func loadMore(item: NotificationItem) async {
|
||||
guard case let .feedLoader(record) = item else { return }
|
||||
guard case let .feedLoader(record) = item, let notification = record.notification else { return }
|
||||
|
||||
let managedObjectContext = context.managedObjectContext
|
||||
let key = "LoadMore@\(record.objectID)"
|
||||
// let managedObjectContext = context.managedObjectContext
|
||||
let key = "LoadMore@\(notification.id)"
|
||||
|
||||
// return when already loading state
|
||||
guard managedObjectContext.cache(froKey: key) == nil else { return }
|
||||
|
||||
guard let feed = record.object(in: managedObjectContext) else { return }
|
||||
guard let maxID = feed.notification?.id else { return }
|
||||
// keep transient property live
|
||||
managedObjectContext.cache(feed, key: key)
|
||||
defer {
|
||||
managedObjectContext.cache(nil, key: key)
|
||||
}
|
||||
// // return when already loading state
|
||||
// guard managedObjectContext.cache(froKey: key) == nil else { return }
|
||||
//
|
||||
// guard let feed = record.object(in: managedObjectContext) else { return }
|
||||
// guard let maxID = feed.notification?.id else { return }
|
||||
// // keep transient property live
|
||||
// managedObjectContext.cache(feed, key: key)
|
||||
// defer {
|
||||
// managedObjectContext.cache(nil, key: key)
|
||||
// }
|
||||
|
||||
// fetch data
|
||||
do {
|
||||
_ = try await context.apiService.notifications(
|
||||
maxID: maxID,
|
||||
maxID: notification.id,
|
||||
scope: scope,
|
||||
authenticationBox: authContext.mastodonAuthenticationBox
|
||||
)
|
||||
|
|
|
@ -32,7 +32,7 @@ extension BookmarkViewModel {
|
|||
|
||||
stateMachine.enter(State.Reloading.self)
|
||||
|
||||
statusFetchedResultsController.$records
|
||||
$statuses
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] records in
|
||||
guard let self = self else { return }
|
||||
|
|
|
@ -57,7 +57,7 @@ extension BookmarkViewModel.State {
|
|||
guard let viewModel = viewModel, let stateMachine = stateMachine else { return }
|
||||
|
||||
// reset
|
||||
viewModel.statusFetchedResultsController.statusIDs = []
|
||||
viewModel.statuses = []
|
||||
|
||||
stateMachine.enter(Loading.self)
|
||||
}
|
||||
|
@ -128,10 +128,10 @@ extension BookmarkViewModel.State {
|
|||
)
|
||||
|
||||
var hasNewStatusesAppend = false
|
||||
var statusIDs = viewModel.statusFetchedResultsController.statusIDs
|
||||
var modifiedStatuses = viewModel.statuses
|
||||
for status in response.value {
|
||||
guard !statusIDs.contains(status.id) else { continue }
|
||||
statusIDs.append(status.id)
|
||||
guard !modifiedStatuses.map({ $0.id }).contains(status.id) else { continue }
|
||||
modifiedStatuses.append(status)
|
||||
hasNewStatusesAppend = true
|
||||
}
|
||||
|
||||
|
@ -147,7 +147,7 @@ extension BookmarkViewModel.State {
|
|||
} else {
|
||||
await enter(state: NoMore.self)
|
||||
}
|
||||
viewModel.statusFetchedResultsController.statusIDs = statusIDs
|
||||
viewModel.statuses = modifiedStatuses
|
||||
} catch {
|
||||
await enter(state: Fail.self)
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ import CoreData
|
|||
import CoreDataStack
|
||||
import GameplayKit
|
||||
import MastodonCore
|
||||
import MastodonSDK
|
||||
|
||||
final class BookmarkViewModel {
|
||||
|
||||
|
@ -20,7 +21,8 @@ final class BookmarkViewModel {
|
|||
let context: AppContext
|
||||
let authContext: AuthContext
|
||||
|
||||
let statusFetchedResultsController: StatusFetchedResultsController
|
||||
// let statusFetchedResultsController: StatusFetchedResultsController
|
||||
@Published var statuses = [Mastodon.Entity.Status]()
|
||||
let listBatchFetchViewModel = ListBatchFetchViewModel()
|
||||
|
||||
// output
|
||||
|
@ -41,11 +43,11 @@ final class BookmarkViewModel {
|
|||
init(context: AppContext, authContext: AuthContext) {
|
||||
self.context = context
|
||||
self.authContext = authContext
|
||||
self.statusFetchedResultsController = StatusFetchedResultsController(
|
||||
managedObjectContext: context.managedObjectContext,
|
||||
domain: authContext.mastodonAuthenticationBox.domain,
|
||||
additionalTweetPredicate: nil
|
||||
)
|
||||
// self.statusFetchedResultsController = StatusFetchedResultsController(
|
||||
// managedObjectContext: context.managedObjectContext,
|
||||
// domain: authContext.mastodonAuthenticationBox.domain,
|
||||
// additionalTweetPredicate: nil
|
||||
// )
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,17 +0,0 @@
|
|||
//
|
||||
// CachedProfileViewModel.swift
|
||||
// Mastodon
|
||||
//
|
||||
// Created by MainasuK Cirno on 2021-3-31.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreDataStack
|
||||
import MastodonCore
|
||||
|
||||
final class CachedProfileViewModel: ProfileViewModel {
|
||||
|
||||
init(context: AppContext, authContext: AuthContext, mastodonUser: MastodonUser) {
|
||||
super.init(context: context, authContext: authContext, optionalMastodonUser: mastodonUser)
|
||||
}
|
||||
}
|
|
@ -20,7 +20,7 @@ extension FamiliarFollowersViewModel {
|
|||
userTableViewCellDelegate: userTableViewCellDelegate
|
||||
)
|
||||
|
||||
userFetchedResultsController.$records
|
||||
$records
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] records in
|
||||
guard let self = self else { return }
|
||||
|
|
|
@ -17,8 +17,8 @@ final class FamiliarFollowersViewModel {
|
|||
// input
|
||||
let context: AppContext
|
||||
let authContext: AuthContext
|
||||
let userFetchedResultsController: UserFetchedResultsController
|
||||
|
||||
// let userFetchedResultsController: UserFetchedResultsController
|
||||
@Published var records = [Mastodon.Entity.Account]()
|
||||
@Published var familiarFollowers: Mastodon.Entity.FamiliarFollowers?
|
||||
|
||||
// output
|
||||
|
@ -27,19 +27,19 @@ final class FamiliarFollowersViewModel {
|
|||
init(context: AppContext, authContext: AuthContext) {
|
||||
self.context = context
|
||||
self.authContext = authContext
|
||||
self.userFetchedResultsController = UserFetchedResultsController(
|
||||
managedObjectContext: context.managedObjectContext,
|
||||
domain: authContext.mastodonAuthenticationBox.domain,
|
||||
additionalPredicate: nil
|
||||
)
|
||||
// self.userFetchedResultsController = UserFetchedResultsController(
|
||||
// managedObjectContext: context.managedObjectContext,
|
||||
// domain: authContext.mastodonAuthenticationBox.domain,
|
||||
// additionalPredicate: nil
|
||||
// )
|
||||
// end init
|
||||
|
||||
$familiarFollowers
|
||||
.map { familiarFollowers -> [MastodonUser.ID] in
|
||||
.map { familiarFollowers in
|
||||
guard let familiarFollowers = familiarFollowers else { return [] }
|
||||
return familiarFollowers.accounts.map { $0.id }
|
||||
return familiarFollowers.accounts
|
||||
}
|
||||
.assign(to: \.userIDs, on: userFetchedResultsController)
|
||||
.assign(to: \.records, on: self)
|
||||
.store(in: &disposeBag)
|
||||
}
|
||||
|
||||
|
|
|
@ -32,7 +32,7 @@ extension FavoriteViewModel {
|
|||
|
||||
stateMachine.enter(State.Reloading.self)
|
||||
|
||||
statusFetchedResultsController.$records
|
||||
$records
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] records in
|
||||
guard let self = self else { return }
|
||||
|
|
|
@ -56,7 +56,7 @@ extension FavoriteViewModel.State {
|
|||
guard let viewModel = viewModel, let stateMachine = stateMachine else { return }
|
||||
|
||||
// reset
|
||||
viewModel.statusFetchedResultsController.statusIDs = []
|
||||
viewModel.records = []
|
||||
|
||||
stateMachine.enter(Loading.self)
|
||||
}
|
||||
|
@ -127,10 +127,10 @@ extension FavoriteViewModel.State {
|
|||
)
|
||||
|
||||
var hasNewStatusesAppend = false
|
||||
var statusIDs = viewModel.statusFetchedResultsController.statusIDs
|
||||
var newRecords = viewModel.records
|
||||
for status in response.value {
|
||||
guard !statusIDs.contains(status.id) else { continue }
|
||||
statusIDs.append(status.id)
|
||||
guard !newRecords.contains(where: { $0.id == status.id }) else { continue }
|
||||
newRecords.append(status)
|
||||
hasNewStatusesAppend = true
|
||||
}
|
||||
|
||||
|
@ -146,7 +146,7 @@ extension FavoriteViewModel.State {
|
|||
} else {
|
||||
await enter(state: NoMore.self)
|
||||
}
|
||||
viewModel.statusFetchedResultsController.statusIDs = statusIDs
|
||||
viewModel.records = newRecords
|
||||
} catch {
|
||||
await enter(state: Fail.self)
|
||||
}
|
||||
|
|
|
@ -7,10 +7,9 @@
|
|||
|
||||
import UIKit
|
||||
import Combine
|
||||
import CoreData
|
||||
import CoreDataStack
|
||||
import GameplayKit
|
||||
import MastodonCore
|
||||
import MastodonSDK
|
||||
|
||||
final class FavoriteViewModel {
|
||||
|
||||
|
@ -19,9 +18,9 @@ final class FavoriteViewModel {
|
|||
// input
|
||||
let context: AppContext
|
||||
let authContext: AuthContext
|
||||
let statusFetchedResultsController: StatusFetchedResultsController
|
||||
let listBatchFetchViewModel = ListBatchFetchViewModel()
|
||||
|
||||
@Published var records = [Mastodon.Entity.Status]()
|
||||
|
||||
// output
|
||||
var diffableDataSource: UITableViewDiffableDataSource<StatusSection, StatusItem>?
|
||||
private(set) lazy var stateMachine: GKStateMachine = {
|
||||
|
@ -40,11 +39,6 @@ final class FavoriteViewModel {
|
|||
init(context: AppContext, authContext: AuthContext) {
|
||||
self.context = context
|
||||
self.authContext = authContext
|
||||
self.statusFetchedResultsController = StatusFetchedResultsController(
|
||||
managedObjectContext: context.managedObjectContext,
|
||||
domain: authContext.mastodonAuthenticationBox.domain,
|
||||
additionalTweetPredicate: nil
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -28,7 +28,7 @@ extension FollowerListViewModel {
|
|||
snapshot.appendItems([.bottomLoader], toSection: .main)
|
||||
diffableDataSource?.applySnapshotUsingReloadData(snapshot, completion: nil)
|
||||
|
||||
userFetchedResultsController.$records
|
||||
$records
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] records in
|
||||
guard let self = self else { return }
|
||||
|
|
|
@ -61,7 +61,7 @@ extension FollowerListViewModel.State {
|
|||
guard let viewModel = viewModel, let stateMachine = stateMachine else { return }
|
||||
|
||||
// reset
|
||||
viewModel.userFetchedResultsController.userIDs = []
|
||||
viewModel.records = []
|
||||
|
||||
stateMachine.enter(Loading.self)
|
||||
}
|
||||
|
@ -139,10 +139,10 @@ extension FollowerListViewModel.State {
|
|||
)
|
||||
|
||||
var hasNewAppend = false
|
||||
var userIDs = viewModel.userFetchedResultsController.userIDs
|
||||
var newRecords = viewModel.records
|
||||
for user in response.value {
|
||||
guard !userIDs.contains(user.id) else { continue }
|
||||
userIDs.append(user.id)
|
||||
guard !newRecords.contains(where: { $0.id == user.id }) else { continue }
|
||||
newRecords.append(user)
|
||||
hasNewAppend = true
|
||||
}
|
||||
|
||||
|
@ -155,7 +155,7 @@ extension FollowerListViewModel.State {
|
|||
}
|
||||
|
||||
self.maxID = maxID
|
||||
viewModel.userFetchedResultsController.userIDs = userIDs
|
||||
viewModel.records = newRecords
|
||||
|
||||
} catch {
|
||||
await enter(state: Fail.self)
|
||||
|
|
|
@ -19,7 +19,8 @@ final class FollowerListViewModel {
|
|||
// input
|
||||
let context: AppContext
|
||||
let authContext: AuthContext
|
||||
let userFetchedResultsController: UserFetchedResultsController
|
||||
// let userFetchedResultsController: UserFetchedResultsController
|
||||
@Published var records = [Mastodon.Entity.Account]()
|
||||
let listBatchFetchViewModel = ListBatchFetchViewModel()
|
||||
|
||||
@Published var domain: String?
|
||||
|
@ -48,11 +49,11 @@ final class FollowerListViewModel {
|
|||
) {
|
||||
self.context = context
|
||||
self.authContext = authContext
|
||||
self.userFetchedResultsController = UserFetchedResultsController(
|
||||
managedObjectContext: context.managedObjectContext,
|
||||
domain: domain,
|
||||
additionalPredicate: nil
|
||||
)
|
||||
// self.userFetchedResultsController = UserFetchedResultsController(
|
||||
// managedObjectContext: context.managedObjectContext,
|
||||
// domain: domain,
|
||||
// additionalPredicate: nil
|
||||
// )
|
||||
self.domain = domain
|
||||
self.userID = userID
|
||||
// end init
|
||||
|
|
|
@ -15,11 +15,10 @@ import MastodonSDK
|
|||
final class MeProfileViewModel: ProfileViewModel {
|
||||
|
||||
init(context: AppContext, authContext: AuthContext) {
|
||||
let user = authContext.mastodonAuthenticationBox.authentication.user(in: context.managedObjectContext)
|
||||
super.init(
|
||||
context: context,
|
||||
authContext: authContext,
|
||||
optionalMastodonUser: user
|
||||
optionalMastodonUser: authContext.mastodonAuthenticationBox.inMemoryCache.meAccount
|
||||
)
|
||||
|
||||
$me
|
||||
|
@ -36,17 +35,7 @@ final class MeProfileViewModel: ProfileViewModel {
|
|||
|
||||
Task {
|
||||
do {
|
||||
|
||||
_ = try await context.apiService.authenticatedUserInfo(authenticationBox: authContext.mastodonAuthenticationBox).value
|
||||
|
||||
try await context.managedObjectContext.performChanges {
|
||||
guard let me = self.authContext.mastodonAuthenticationBox.authentication.user(in: self.context.managedObjectContext) else {
|
||||
assertionFailure()
|
||||
return
|
||||
}
|
||||
|
||||
self.me = me
|
||||
}
|
||||
self.me = try await context.apiService.authenticatedUserInfo(authenticationBox: authContext.mastodonAuthenticationBox).value
|
||||
} catch {
|
||||
// do nothing?
|
||||
}
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
|
||||
import UIKit
|
||||
import Combine
|
||||
import CoreDataStack
|
||||
import MastodonSDK
|
||||
import MastodonMeta
|
||||
import MastodonAsset
|
||||
|
@ -33,8 +32,8 @@ class ProfileViewModel: NSObject {
|
|||
// input
|
||||
let context: AppContext
|
||||
let authContext: AuthContext
|
||||
@Published var me: MastodonUser?
|
||||
@Published var user: MastodonUser?
|
||||
@Published var me: Mastodon.Entity.Account?
|
||||
@Published var user: Mastodon.Entity.Account?
|
||||
|
||||
let viewDidAppear = PassthroughSubject<Void, Never>()
|
||||
|
||||
|
@ -56,7 +55,7 @@ class ProfileViewModel: NSObject {
|
|||
// @Published var protected: Bool? = nil
|
||||
// let needsPagePinToTop = CurrentValueSubject<Bool, Never>(false)
|
||||
|
||||
init(context: AppContext, authContext: AuthContext, optionalMastodonUser mastodonUser: MastodonUser?) {
|
||||
init(context: AppContext, authContext: AuthContext, optionalMastodonUser mastodonUser: Mastodon.Entity.Account?) {
|
||||
self.context = context
|
||||
self.authContext = authContext
|
||||
self.user = mastodonUser
|
||||
|
@ -82,7 +81,9 @@ class ProfileViewModel: NSObject {
|
|||
super.init()
|
||||
|
||||
// bind me
|
||||
self.me = authContext.mastodonAuthenticationBox.authentication.user(in: context.managedObjectContext)
|
||||
relationshipViewModel.me = authContext.mastodonAuthenticationBox.inMemoryCache.meAccount
|
||||
|
||||
|
||||
$me
|
||||
.assign(to: \.me, on: relationshipViewModel)
|
||||
.store(in: &disposeBag)
|
||||
|
@ -91,7 +92,8 @@ class ProfileViewModel: NSObject {
|
|||
$user
|
||||
.map { user -> UserIdentifier? in
|
||||
guard let user = user else { return nil }
|
||||
return MastodonUserIdentifier(domain: user.domain, userID: user.id)
|
||||
#warning("fix domain!")
|
||||
return MastodonUserIdentifier(domain: user.domain!, userID: user.id)
|
||||
}
|
||||
.assign(to: &$userIdentifier)
|
||||
$user
|
||||
|
@ -122,14 +124,11 @@ class ProfileViewModel: NSObject {
|
|||
.store(in: &disposeBag)
|
||||
|
||||
// query relationship
|
||||
let userRecord = $user.map { user -> ManagedObjectRecord<MastodonUser>? in
|
||||
user.flatMap { ManagedObjectRecord<MastodonUser>(objectID: $0.objectID) }
|
||||
}
|
||||
let pendingRetryPublisher = CurrentValueSubject<TimeInterval, Never>(1)
|
||||
|
||||
// observe friendship
|
||||
Publishers.CombineLatest(
|
||||
userRecord,
|
||||
$user,
|
||||
pendingRetryPublisher
|
||||
)
|
||||
.sink { [weak self] userRecord, _ in
|
||||
|
@ -178,18 +177,17 @@ class ProfileViewModel: NSObject {
|
|||
|
||||
// fetch profile info before edit
|
||||
func fetchEditProfileInfo() -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Account>, Error> {
|
||||
guard let me = me,
|
||||
let mastodonAuthentication = me.mastodonAuthentication
|
||||
guard let me = me
|
||||
else {
|
||||
return Fail(error: APIService.APIError.implicit(.authenticationMissing)).eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
let authorization = Mastodon.API.OAuth.Authorization(accessToken: mastodonAuthentication.userAccessToken)
|
||||
return context.apiService.accountVerifyCredentials(domain: me.domain, authorization: authorization)
|
||||
let authorization = Mastodon.API.OAuth.Authorization(accessToken: authContext.mastodonAuthenticationBox.userAuthorization.accessToken)
|
||||
return context.apiService.accountVerifyCredentials(domain: authContext.mastodonAuthenticationBox.domain, authorization: authorization)
|
||||
}
|
||||
|
||||
private func updateRelationship(
|
||||
record: ManagedObjectRecord<MastodonUser>,
|
||||
record: Mastodon.Entity.Account,
|
||||
authenticationBox: MastodonAuthenticationBox
|
||||
) async throws -> Mastodon.Response.Content<[Mastodon.Entity.Relationship]> {
|
||||
let response = try await context.apiService.relationship(
|
||||
|
|
|
@ -38,15 +38,7 @@ final class RemoteProfileViewModel: ProfileViewModel {
|
|||
}
|
||||
} receiveValue: { [weak self] response in
|
||||
guard let self = self else { return }
|
||||
let managedObjectContext = context.managedObjectContext
|
||||
let request = MastodonUser.sortedFetchRequest
|
||||
request.fetchLimit = 1
|
||||
request.predicate = MastodonUser.predicate(domain: domain, id: response.value.id)
|
||||
guard let mastodonUser = managedObjectContext.safeFetch(request).first else {
|
||||
assertionFailure()
|
||||
return
|
||||
}
|
||||
self.user = mastodonUser
|
||||
self.user = response.value
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
}
|
||||
|
@ -59,33 +51,8 @@ final class RemoteProfileViewModel: ProfileViewModel {
|
|||
notificationID: notificationID,
|
||||
authenticationBox: authContext.mastodonAuthenticationBox
|
||||
)
|
||||
let userID = response.value.account.id
|
||||
|
||||
let _user: MastodonUser? = try await context.managedObjectContext.perform {
|
||||
let request = MastodonUser.sortedFetchRequest
|
||||
request.predicate = MastodonUser.predicate(domain: authContext.mastodonAuthenticationBox.domain, id: userID)
|
||||
request.fetchLimit = 1
|
||||
return context.managedObjectContext.safeFetch(request).first
|
||||
}
|
||||
|
||||
if let user = _user {
|
||||
self.user = user
|
||||
} else {
|
||||
_ = try await context.apiService.accountInfo(
|
||||
domain: authContext.mastodonAuthenticationBox.domain,
|
||||
userID: userID,
|
||||
authorization: authContext.mastodonAuthenticationBox.userAuthorization
|
||||
)
|
||||
|
||||
let _user: MastodonUser? = try await context.managedObjectContext.perform {
|
||||
let request = MastodonUser.sortedFetchRequest
|
||||
request.predicate = MastodonUser.predicate(domain: authContext.mastodonAuthenticationBox.domain, id: userID)
|
||||
request.fetchLimit = 1
|
||||
return context.managedObjectContext.safeFetch(request).first
|
||||
}
|
||||
|
||||
self.user = _user
|
||||
}
|
||||
self.user = response.value.account
|
||||
} // end Task
|
||||
}
|
||||
|
||||
|
@ -114,15 +81,7 @@ final class RemoteProfileViewModel: ProfileViewModel {
|
|||
}
|
||||
} receiveValue: { [weak self] response in
|
||||
guard let self = self, let value = response.value else { return }
|
||||
let managedObjectContext = context.managedObjectContext
|
||||
let request = MastodonUser.sortedFetchRequest
|
||||
request.fetchLimit = 1
|
||||
request.predicate = MastodonUser.predicate(domain: domain, id: value.id)
|
||||
guard let mastodonUser = managedObjectContext.safeFetch(request).first else {
|
||||
assertionFailure()
|
||||
return
|
||||
}
|
||||
self.user = mastodonUser
|
||||
self.user = value
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
}
|
||||
|
|
|
@ -48,7 +48,7 @@ extension UserTimelineViewModel {
|
|||
).map { $0 || $1 || $2 }
|
||||
|
||||
Publishers.CombineLatest(
|
||||
statusFetchedResultsController.$records,
|
||||
$records,
|
||||
needsTimelineHidden.removeDuplicates()
|
||||
)
|
||||
.debounce(for: .milliseconds(300), scheduler: DispatchQueue.main)
|
||||
|
|
|
@ -56,7 +56,7 @@ extension UserTimelineViewModel.State {
|
|||
guard let viewModel = viewModel, let stateMachine = stateMachine else { return }
|
||||
|
||||
// reset
|
||||
viewModel.statusFetchedResultsController.statusIDs = []
|
||||
viewModel.records = []
|
||||
|
||||
stateMachine.enter(Loading.self)
|
||||
}
|
||||
|
@ -112,7 +112,7 @@ extension UserTimelineViewModel.State {
|
|||
super.didEnter(from: previousState)
|
||||
guard let viewModel = viewModel, let stateMachine = stateMachine else { return }
|
||||
|
||||
let maxID = viewModel.statusFetchedResultsController.statusIDs.last
|
||||
let maxID = viewModel.records.last?.id
|
||||
|
||||
guard let userID = viewModel.userIdentifier?.userID, !userID.isEmpty else {
|
||||
stateMachine.enter(Fail.self)
|
||||
|
@ -135,10 +135,10 @@ extension UserTimelineViewModel.State {
|
|||
)
|
||||
|
||||
var hasNewStatusesAppend = false
|
||||
var statusIDs = viewModel.statusFetchedResultsController.statusIDs
|
||||
var newRecords = viewModel.records
|
||||
for status in response.value {
|
||||
guard !statusIDs.contains(status.id) else { continue }
|
||||
statusIDs.append(status.id)
|
||||
guard !newRecords.contains(where: { $0.id == status.id }) else { continue }
|
||||
newRecords.append(status)
|
||||
hasNewStatusesAppend = true
|
||||
}
|
||||
|
||||
|
@ -147,7 +147,7 @@ extension UserTimelineViewModel.State {
|
|||
} else {
|
||||
await enter(state: NoMore.self)
|
||||
}
|
||||
viewModel.statusFetchedResultsController.statusIDs = statusIDs
|
||||
viewModel.records = newRecords
|
||||
|
||||
} catch {
|
||||
await enter(state: Fail.self)
|
||||
|
|
|
@ -21,7 +21,7 @@ final class UserTimelineViewModel {
|
|||
let context: AppContext
|
||||
let authContext: AuthContext
|
||||
let title: String
|
||||
let statusFetchedResultsController: StatusFetchedResultsController
|
||||
// let statusFetchedResultsController: StatusFetchedResultsController
|
||||
let listBatchFetchViewModel = ListBatchFetchViewModel()
|
||||
@Published var userIdentifier: UserIdentifier?
|
||||
@Published var queryFilter: QueryFilter
|
||||
|
@ -32,7 +32,8 @@ final class UserTimelineViewModel {
|
|||
|
||||
// let userDisplayName = CurrentValueSubject<String?, Never>(nil) // for suspended prompt label
|
||||
// var dataSourceDidUpdate = PassthroughSubject<Void, Never>()
|
||||
|
||||
@Published var records = [Mastodon.Entity.Status]()
|
||||
|
||||
// output
|
||||
var diffableDataSource: UITableViewDiffableDataSource<StatusSection, StatusItem>?
|
||||
private(set) lazy var stateMachine: GKStateMachine = {
|
||||
|
@ -57,11 +58,11 @@ final class UserTimelineViewModel {
|
|||
self.context = context
|
||||
self.authContext = authContext
|
||||
self.title = title
|
||||
self.statusFetchedResultsController = StatusFetchedResultsController(
|
||||
managedObjectContext: context.managedObjectContext,
|
||||
domain: authContext.mastodonAuthenticationBox.domain,
|
||||
additionalTweetPredicate: nil
|
||||
)
|
||||
// self.statusFetchedResultsController = StatusFetchedResultsController(
|
||||
// managedObjectContext: context.managedObjectContext,
|
||||
// domain: authContext.mastodonAuthenticationBox.domain,
|
||||
// additionalTweetPredicate: nil
|
||||
// )
|
||||
self.queryFilter = queryFilter
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,7 +33,7 @@ extension UserListViewModel {
|
|||
// trigger initial loading
|
||||
stateMachine.enter(UserListViewModel.State.Reloading.self)
|
||||
|
||||
userFetchedResultsController.$records
|
||||
$records
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] records in
|
||||
guard let self = self else { return }
|
||||
|
|
|
@ -55,7 +55,7 @@ extension UserListViewModel.State {
|
|||
guard let viewModel = viewModel, let stateMachine = stateMachine else { return }
|
||||
|
||||
// reset
|
||||
viewModel.userFetchedResultsController.userIDs = []
|
||||
viewModel.records = []
|
||||
|
||||
stateMachine.enter(Loading.self)
|
||||
}
|
||||
|
@ -140,10 +140,10 @@ extension UserListViewModel.State {
|
|||
}
|
||||
|
||||
var hasNewAppend = false
|
||||
var userIDs = viewModel.userFetchedResultsController.userIDs
|
||||
var newRecords = viewModel.records
|
||||
for user in response.value {
|
||||
guard !userIDs.contains(user.id) else { continue }
|
||||
userIDs.append(user.id)
|
||||
guard !newRecords.contains(where: { $0.id == user.id }) else { continue }
|
||||
newRecords.append(user)
|
||||
hasNewAppend = true
|
||||
}
|
||||
|
||||
|
@ -155,7 +155,7 @@ extension UserListViewModel.State {
|
|||
await enter(state: NoMore.self)
|
||||
}
|
||||
self.maxID = maxID
|
||||
viewModel.userFetchedResultsController.userIDs = userIDs
|
||||
viewModel.records = newRecords
|
||||
|
||||
} catch {
|
||||
await enter(state: Fail.self)
|
||||
|
@ -179,7 +179,8 @@ extension UserListViewModel.State {
|
|||
|
||||
guard let viewModel = viewModel else { return }
|
||||
// trigger reload
|
||||
viewModel.userFetchedResultsController.userIDs = viewModel.userFetchedResultsController.userIDs
|
||||
// viewModel.userFetchedResultsController.userIDs = viewModel.userFetchedResultsController.userIDs
|
||||
stateMachine?.enter(Loading.self)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,9 +7,9 @@
|
|||
|
||||
import UIKit
|
||||
import Combine
|
||||
import CoreDataStack
|
||||
import GameplayKit
|
||||
import MastodonCore
|
||||
import MastodonSDK
|
||||
|
||||
final class UserListViewModel {
|
||||
var disposeBag = Set<AnyCancellable>()
|
||||
|
@ -18,9 +18,10 @@ final class UserListViewModel {
|
|||
let context: AppContext
|
||||
let authContext: AuthContext
|
||||
let kind: Kind
|
||||
let userFetchedResultsController: UserFetchedResultsController
|
||||
// let userFetchedResultsController: UserFetchedResultsController
|
||||
let listBatchFetchViewModel = ListBatchFetchViewModel()
|
||||
|
||||
@Published var records = [Mastodon.Entity.Account]()
|
||||
|
||||
// output
|
||||
var diffableDataSource: UITableViewDiffableDataSource<UserSection, UserItem>!
|
||||
@MainActor private(set) lazy var stateMachine: GKStateMachine = {
|
||||
|
@ -43,11 +44,11 @@ final class UserListViewModel {
|
|||
self.context = context
|
||||
self.authContext = authContext
|
||||
self.kind = kind
|
||||
self.userFetchedResultsController = UserFetchedResultsController(
|
||||
managedObjectContext: context.managedObjectContext,
|
||||
domain: authContext.mastodonAuthenticationBox.domain,
|
||||
additionalPredicate: nil
|
||||
)
|
||||
// self.userFetchedResultsController = UserFetchedResultsController(
|
||||
// managedObjectContext: context.managedObjectContext,
|
||||
// domain: authContext.mastodonAuthenticationBox.domain,
|
||||
// additionalPredicate: nil
|
||||
// )
|
||||
// end init
|
||||
}
|
||||
}
|
||||
|
@ -55,7 +56,7 @@ final class UserListViewModel {
|
|||
extension UserListViewModel {
|
||||
// TODO: refactor follower and following into user list
|
||||
enum Kind {
|
||||
case rebloggedBy(status: ManagedObjectRecord<Status>)
|
||||
case favoritedBy(status: ManagedObjectRecord<Status>)
|
||||
case rebloggedBy(status: Mastodon.Entity.Status)
|
||||
case favoritedBy(status: Mastodon.Entity.Status)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,8 +6,6 @@
|
|||
//
|
||||
|
||||
import Combine
|
||||
import CoreData
|
||||
import CoreDataStack
|
||||
import Foundation
|
||||
import GameplayKit
|
||||
import MastodonSDK
|
||||
|
@ -28,8 +26,8 @@ class ReportViewModel {
|
|||
// input
|
||||
let context: AppContext
|
||||
let authContext: AuthContext
|
||||
let user: ManagedObjectRecord<MastodonUser>
|
||||
let status: ManagedObjectRecord<Status>?
|
||||
let user: Mastodon.Entity.Account
|
||||
let status: Mastodon.Entity.Status?
|
||||
|
||||
// output
|
||||
@Published var isReporting = false
|
||||
|
@ -38,8 +36,8 @@ class ReportViewModel {
|
|||
init(
|
||||
context: AppContext,
|
||||
authContext: AuthContext,
|
||||
user: ManagedObjectRecord<MastodonUser>,
|
||||
status: ManagedObjectRecord<Status>?
|
||||
user: Mastodon.Entity.Account,
|
||||
status: Mastodon.Entity.Status?
|
||||
) {
|
||||
self.context = context
|
||||
self.authContext = authContext
|
||||
|
@ -56,16 +54,7 @@ class ReportViewModel {
|
|||
reportReasonViewModel.headline = L10n.Scene.Report.StepOne.whatsWrongWithThisPost
|
||||
} else {
|
||||
Task { @MainActor in
|
||||
let managedObjectContext = context.managedObjectContext
|
||||
let _username: String? = try? await managedObjectContext.perform {
|
||||
let user = user.object(in: managedObjectContext)
|
||||
return user?.acctWithDomain
|
||||
}
|
||||
if let username = _username {
|
||||
reportReasonViewModel.headline = L10n.Scene.Report.StepOne.whatsWrongWithThisUsername(username)
|
||||
} else {
|
||||
reportReasonViewModel.headline = L10n.Scene.Report.StepOne.whatsWrongWithThisAccount
|
||||
}
|
||||
reportReasonViewModel.headline = L10n.Scene.Report.StepOne.whatsWrongWithThisUsername(user.acctWithDomain)
|
||||
} // end Task
|
||||
}
|
||||
|
||||
|
@ -95,64 +84,56 @@ extension ReportViewModel {
|
|||
func report() async throws {
|
||||
guard !isReporting else { return }
|
||||
|
||||
let managedObjectContext = context.managedObjectContext
|
||||
let _query: Mastodon.API.Reports.FileReportQuery? = try await managedObjectContext.perform {
|
||||
guard let user = self.user.object(in: managedObjectContext) else { return nil }
|
||||
|
||||
// the status picker is essential step in report flow
|
||||
// only check isSkip or not
|
||||
let statusIDs: [Status.ID]? = {
|
||||
if self.reportStatusViewModel.isSkip {
|
||||
let _id: Status.ID? = self.reportStatusViewModel.status.flatMap { record -> Status.ID? in
|
||||
guard let status = record.object(in: managedObjectContext) else { return nil }
|
||||
return status.id
|
||||
}
|
||||
return _id.flatMap { [$0] }
|
||||
} else {
|
||||
return self.reportStatusViewModel.selectStatuses.compactMap { record -> Status.ID? in
|
||||
guard let status = record.object(in: managedObjectContext) else { return nil }
|
||||
return status.id
|
||||
}
|
||||
// the status picker is essential step in report flow
|
||||
// only check isSkip or not
|
||||
let statusIDs: [Mastodon.Entity.Status.ID]? = {
|
||||
if self.reportStatusViewModel.isSkip {
|
||||
let _id: Mastodon.Entity.Status.ID? = self.reportStatusViewModel.status.flatMap { record -> Mastodon.Entity.Status.ID? in
|
||||
return record.id
|
||||
}
|
||||
}()
|
||||
|
||||
// the user comment is essential step in report flow
|
||||
// only check isSkip or not
|
||||
let comment: String? = {
|
||||
let _comment = self.reportSupplementaryViewModel.isSkip ? nil : self.reportSupplementaryViewModel.commentContext.comment
|
||||
if let comment = _comment, !comment.isEmpty {
|
||||
return comment
|
||||
} else {
|
||||
return _id.flatMap { [$0] } ?? []
|
||||
} else {
|
||||
return self.reportStatusViewModel.selectStatuses.compactMap { record -> Mastodon.Entity.Status.ID? in
|
||||
return record.id
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// the user comment is essential step in report flow
|
||||
// only check isSkip or not
|
||||
let comment: String? = {
|
||||
let _comment = self.reportSupplementaryViewModel.isSkip ? nil : self.reportSupplementaryViewModel.commentContext.comment
|
||||
if let comment = _comment, !comment.isEmpty {
|
||||
return comment
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}()
|
||||
|
||||
let query = Mastodon.API.Reports.FileReportQuery(
|
||||
accountID: user.id,
|
||||
statusIDs: statusIDs,
|
||||
comment: comment,
|
||||
forward: true,
|
||||
category: {
|
||||
switch self.reportReasonViewModel.selectReason {
|
||||
case .dislike: return nil
|
||||
case .spam: return .spam
|
||||
case .violateRule: return .violation
|
||||
case .other: return .other
|
||||
case .none: return nil
|
||||
}
|
||||
}(),
|
||||
ruleIDs: {
|
||||
switch self.reportReasonViewModel.selectReason {
|
||||
case .violateRule:
|
||||
let ruleIDs = self.reportServerRulesViewModel.selectRules.map { $0.id }.sorted()
|
||||
return ruleIDs
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}()
|
||||
return Mastodon.API.Reports.FileReportQuery(
|
||||
accountID: user.id,
|
||||
statusIDs: statusIDs,
|
||||
comment: comment,
|
||||
forward: true,
|
||||
category: {
|
||||
switch self.reportReasonViewModel.selectReason {
|
||||
case .dislike: return nil
|
||||
case .spam: return .spam
|
||||
case .violateRule: return .violation
|
||||
case .other: return .other
|
||||
case .none: return nil
|
||||
}
|
||||
}(),
|
||||
ruleIDs: {
|
||||
switch self.reportReasonViewModel.selectReason {
|
||||
case .violateRule:
|
||||
let ruleIDs = self.reportServerRulesViewModel.selectRules.map { $0.id }.sorted()
|
||||
return ruleIDs
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}()
|
||||
)
|
||||
}
|
||||
|
||||
guard let query = _query else { return }
|
||||
)
|
||||
|
||||
do {
|
||||
isReporting = true
|
||||
|
|
|
@ -6,8 +6,6 @@
|
|||
//
|
||||
|
||||
import Combine
|
||||
import CoreData
|
||||
import CoreDataStack
|
||||
import Foundation
|
||||
import MastodonSDK
|
||||
import UIKit
|
||||
|
@ -23,7 +21,7 @@ class ReportResultViewModel: ObservableObject {
|
|||
// input
|
||||
let context: AppContext
|
||||
let authContext: AuthContext
|
||||
let user: ManagedObjectRecord<MastodonUser>
|
||||
let user: Mastodon.Entity.Account
|
||||
let isReported: Bool
|
||||
|
||||
var headline: String {
|
||||
|
@ -48,7 +46,7 @@ class ReportResultViewModel: ObservableObject {
|
|||
init(
|
||||
context: AppContext,
|
||||
authContext: AuthContext,
|
||||
user: ManagedObjectRecord<MastodonUser>,
|
||||
user: Mastodon.Entity.Account,
|
||||
isReported: Bool
|
||||
) {
|
||||
self.context = context
|
||||
|
@ -58,10 +56,8 @@ class ReportResultViewModel: ObservableObject {
|
|||
// end init
|
||||
|
||||
Task { @MainActor in
|
||||
guard let user = user.object(in: context.managedObjectContext) else { return }
|
||||
guard let me = authContext.mastodonAuthenticationBox.authentication.user(in: context.managedObjectContext) else { return }
|
||||
self.relationshipViewModel.user = user
|
||||
self.relationshipViewModel.me = me
|
||||
self.relationshipViewModel.me = authContext.mastodonAuthenticationBox.inMemoryCache.meAccount
|
||||
|
||||
self.avatarURL = user.avatarImageURL()
|
||||
self.username = user.acctWithDomain
|
||||
|
|
|
@ -32,7 +32,7 @@ extension ReportStatusViewModel {
|
|||
snapshot.appendSections([.main])
|
||||
diffableDataSource?.apply(snapshot)
|
||||
|
||||
statusFetchedResultsController.$records
|
||||
$records
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] records in
|
||||
guard let self = self else { return }
|
||||
|
|
|
@ -7,9 +7,8 @@
|
|||
|
||||
import func QuartzCore.CACurrentMediaTime
|
||||
import Foundation
|
||||
import CoreData
|
||||
import CoreDataStack
|
||||
import GameplayKit
|
||||
import MastodonSDK
|
||||
|
||||
extension ReportStatusViewModel {
|
||||
class State: GKState {
|
||||
|
@ -64,14 +63,11 @@ extension ReportStatusViewModel.State {
|
|||
super.didEnter(from: previousState)
|
||||
guard let viewModel else { return }
|
||||
|
||||
let maxID = viewModel.statusFetchedResultsController.statusIDs.last
|
||||
let maxID = viewModel.records.last?.id
|
||||
|
||||
Task {
|
||||
let managedObjectContext = viewModel.context.managedObjectContext
|
||||
let _userID: MastodonUser.ID? = try await managedObjectContext.perform {
|
||||
guard let user = viewModel.user.object(in: managedObjectContext) else { return nil }
|
||||
return user.id
|
||||
}
|
||||
let _userID: Mastodon.Entity.Account.ID? = viewModel.user.id
|
||||
guard let userID = _userID else {
|
||||
await enter(state: Fail.self)
|
||||
return
|
||||
|
@ -89,10 +85,10 @@ extension ReportStatusViewModel.State {
|
|||
)
|
||||
|
||||
var hasNewStatusesAppend = false
|
||||
var statusIDs = viewModel.statusFetchedResultsController.statusIDs
|
||||
var newRecords = viewModel.records
|
||||
for status in response.value {
|
||||
guard !statusIDs.contains(status.id) else { continue }
|
||||
statusIDs.append(status.id)
|
||||
guard !newRecords.contains(where: { $0.id == status.id }) else { continue }
|
||||
newRecords.append(status)
|
||||
hasNewStatusesAppend = true
|
||||
}
|
||||
|
||||
|
@ -101,7 +97,7 @@ extension ReportStatusViewModel.State {
|
|||
} else {
|
||||
await enter(state: NoMore.self)
|
||||
}
|
||||
viewModel.statusFetchedResultsController.statusIDs = statusIDs
|
||||
viewModel.records = newRecords
|
||||
|
||||
} catch {
|
||||
await enter(state: Fail.self)
|
||||
|
|
|
@ -6,8 +6,6 @@
|
|||
//
|
||||
|
||||
import Combine
|
||||
import CoreData
|
||||
import CoreDataStack
|
||||
import Foundation
|
||||
import GameplayKit
|
||||
import MastodonSDK
|
||||
|
@ -24,14 +22,15 @@ class ReportStatusViewModel {
|
|||
// input
|
||||
let context: AppContext
|
||||
let authContext: AuthContext
|
||||
let user: ManagedObjectRecord<MastodonUser>
|
||||
let status: ManagedObjectRecord<Status>?
|
||||
let statusFetchedResultsController: StatusFetchedResultsController
|
||||
let user: Mastodon.Entity.Account
|
||||
let status: Mastodon.Entity.Status?
|
||||
// let statusFetchedResultsController: StatusFetchedResultsController
|
||||
let listBatchFetchViewModel = ListBatchFetchViewModel()
|
||||
|
||||
@Published var isSkip = false
|
||||
@Published var selectStatuses = OrderedSet<ManagedObjectRecord<Status>>()
|
||||
|
||||
@Published var selectStatuses = OrderedSet<Mastodon.Entity.Status>()
|
||||
@Published var records = [Mastodon.Entity.Status]()
|
||||
|
||||
// output
|
||||
var diffableDataSource: UITableViewDiffableDataSource<ReportSection, ReportItem>?
|
||||
private(set) lazy var stateMachine: GKStateMachine = {
|
||||
|
@ -51,18 +50,18 @@ class ReportStatusViewModel {
|
|||
init(
|
||||
context: AppContext,
|
||||
authContext: AuthContext,
|
||||
user: ManagedObjectRecord<MastodonUser>,
|
||||
status: ManagedObjectRecord<Status>?
|
||||
user: Mastodon.Entity.Account,
|
||||
status: Mastodon.Entity.Status?
|
||||
) {
|
||||
self.context = context
|
||||
self.authContext = authContext
|
||||
self.user = user
|
||||
self.status = status
|
||||
self.statusFetchedResultsController = StatusFetchedResultsController(
|
||||
managedObjectContext: context.managedObjectContext,
|
||||
domain: authContext.mastodonAuthenticationBox.domain,
|
||||
additionalTweetPredicate: nil
|
||||
)
|
||||
// self.statusFetchedResultsController = StatusFetchedResultsController(
|
||||
// managedObjectContext: context.managedObjectContext,
|
||||
// domain: authContext.mastodonAuthenticationBox.domain,
|
||||
// additionalTweetPredicate: nil
|
||||
// )
|
||||
// end init
|
||||
|
||||
if let status = status {
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
|
||||
import UIKit
|
||||
import Combine
|
||||
import CoreDataStack
|
||||
import MastodonCore
|
||||
import MastodonSDK
|
||||
|
||||
|
@ -18,7 +17,7 @@ class ReportSupplementaryViewModel {
|
|||
// Input
|
||||
let context: AppContext
|
||||
let authContext: AuthContext
|
||||
let user: ManagedObjectRecord<MastodonUser>
|
||||
let user: Mastodon.Entity.Account
|
||||
let commentContext = ReportItem.CommentContext()
|
||||
|
||||
@Published var isSkip = false
|
||||
|
@ -31,7 +30,7 @@ class ReportSupplementaryViewModel {
|
|||
init(
|
||||
context: AppContext,
|
||||
authContext: AuthContext,
|
||||
user: ManagedObjectRecord<MastodonUser>
|
||||
user: Mastodon.Entity.Account
|
||||
) {
|
||||
self.context = context
|
||||
self.authContext = authContext
|
||||
|
|
|
@ -6,13 +6,13 @@
|
|||
//
|
||||
|
||||
import UIKit
|
||||
import CoreDataStack
|
||||
import MastodonSDK
|
||||
|
||||
extension ReportStatusTableViewCell {
|
||||
final class ViewModel {
|
||||
let value: Status
|
||||
let value: Mastodon.Entity.Status
|
||||
|
||||
init(value: Status) {
|
||||
init(value: Mastodon.Entity.Status) {
|
||||
self.value = value
|
||||
}
|
||||
}
|
||||
|
|
|
@ -75,22 +75,10 @@ extension SearchResultOverviewCoordinator: SearchResultsOverviewTableViewControl
|
|||
showProfile(viewController, for: account)
|
||||
} else if let status = searchResult.statuses.first {
|
||||
|
||||
let status = try await managedObjectContext.perform {
|
||||
return Persistence.Status.fetch(in: managedObjectContext, context: Persistence.Status.PersistContext(
|
||||
domain: authContext.mastodonAuthenticationBox.domain,
|
||||
entity: status,
|
||||
me: authContext.mastodonAuthenticationBox.authentication.user(in: managedObjectContext),
|
||||
statusCache: nil,
|
||||
userCache: nil,
|
||||
networkDate: Date()))
|
||||
}
|
||||
|
||||
guard let status else { return }
|
||||
|
||||
await DataSourceFacade.coordinateToStatusThreadScene(
|
||||
provider: viewController,
|
||||
target: .status, // remove reblog wrapper
|
||||
status: status.asRecord
|
||||
status: status
|
||||
)
|
||||
} else if let url = URL(string: urlString) {
|
||||
let prefixedURL: URL?
|
||||
|
@ -111,27 +99,12 @@ extension SearchResultOverviewCoordinator: SearchResultsOverviewTableViewControl
|
|||
}
|
||||
|
||||
func showProfile(_ viewController: SearchResultsOverviewTableViewController, for account: Mastodon.Entity.Account) {
|
||||
let managedObjectContext = context.managedObjectContext
|
||||
let domain = authContext.mastodonAuthenticationBox.domain
|
||||
|
||||
Task {
|
||||
let user = try await managedObjectContext.perform {
|
||||
return Persistence.MastodonUser.fetch(in: managedObjectContext,
|
||||
context: Persistence.MastodonUser.PersistContext(
|
||||
domain: domain,
|
||||
entity: account,
|
||||
cache: nil,
|
||||
networkDate: Date()
|
||||
))
|
||||
}
|
||||
await DataSourceFacade.coordinateToProfileScene(provider: viewController,
|
||||
user: account)
|
||||
|
||||
if let user {
|
||||
await DataSourceFacade.coordinateToProfileScene(provider: viewController,
|
||||
user: user.asRecord)
|
||||
|
||||
await DataSourceFacade.responseToCreateSearchHistory(provider: viewController,
|
||||
item: .user(record: user.asRecord))
|
||||
}
|
||||
await DataSourceFacade.responseToCreateSearchHistory(provider: viewController,
|
||||
item: .user(record: account))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -6,10 +6,9 @@
|
|||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
import CoreDataStack
|
||||
import MastodonSDK
|
||||
|
||||
enum SearchHistoryItem: Hashable {
|
||||
case hashtag(ManagedObjectRecord<Tag>)
|
||||
case user(ManagedObjectRecord<MastodonUser>)
|
||||
case hashtag(Mastodon.Entity.Tag)
|
||||
case user(Mastodon.Entity.Account)
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ extension SearchHistoryViewController: DataSourceProvider {
|
|||
case .user(let record):
|
||||
return .user(record: record)
|
||||
case .hashtag(let record):
|
||||
return .hashtag(tag: .record(record))
|
||||
return .hashtag(tag: .entity(record))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -27,7 +27,7 @@ extension SearchHistoryViewModel {
|
|||
snapshot.appendSections([.main])
|
||||
diffableDataSource?.apply(snapshot, animatingDifferences: false)
|
||||
|
||||
searchHistoryFetchedResultController.$records
|
||||
$records
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] records in
|
||||
guard let self = self else { return }
|
||||
|
@ -35,23 +35,24 @@ extension SearchHistoryViewModel {
|
|||
|
||||
Task {
|
||||
do {
|
||||
let managedObjectContext = self.context.managedObjectContext
|
||||
let items: [SearchHistoryItem] = try await managedObjectContext.perform {
|
||||
var items: [SearchHistoryItem] = []
|
||||
// let managedObjectContext = self.context.managedObjectContext
|
||||
// let items: [SearchHistoryItem] = try await managedObjectContext.perform {
|
||||
// var items: [SearchHistoryItem] = []
|
||||
//
|
||||
// for record in records {
|
||||
// guard let searchHistory = record.object(in: managedObjectContext) else { continue }
|
||||
// if let user = searchHistory.account {
|
||||
// items.append(.user(.init(objectID: user.objectID)))
|
||||
// } else if let hashtag = searchHistory.hashtag {
|
||||
// items.append(.hashtag(.init(objectID: hashtag.objectID)))
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// return items
|
||||
// }
|
||||
|
||||
for record in records {
|
||||
guard let searchHistory = record.object(in: managedObjectContext) else { continue }
|
||||
if let user = searchHistory.account {
|
||||
items.append(.user(.init(objectID: user.objectID)))
|
||||
} else if let hashtag = searchHistory.hashtag {
|
||||
items.append(.hashtag(.init(objectID: hashtag.objectID)))
|
||||
}
|
||||
}
|
||||
|
||||
return items
|
||||
}
|
||||
|
||||
let mostRecentItems = Array(items.prefix(10))
|
||||
#warning("Reimplement storing and recovering search history")
|
||||
let mostRecentItems = [SearchHistoryItem]() //Array(items.prefix(10))
|
||||
var snapshot = NSDiffableDataSourceSnapshot<SearchHistorySection, SearchHistoryItem>()
|
||||
snapshot.appendSections([.main])
|
||||
snapshot.appendItems(mostRecentItems, toSection: .main)
|
||||
|
|
|
@ -7,8 +7,12 @@
|
|||
|
||||
import UIKit
|
||||
import Combine
|
||||
import CoreDataStack
|
||||
import MastodonCore
|
||||
import MastodonSDK
|
||||
|
||||
struct SearchHistoryQueryItem {
|
||||
|
||||
}
|
||||
|
||||
final class SearchHistoryViewModel {
|
||||
var disposeBag = Set<AnyCancellable>()
|
||||
|
@ -16,18 +20,13 @@ final class SearchHistoryViewModel {
|
|||
// input
|
||||
let context: AppContext
|
||||
let authContext: AuthContext
|
||||
let searchHistoryFetchedResultController: SearchHistoryFetchedResultController
|
||||
|
||||
@Published var records = [SearchHistoryQueryItem]()
|
||||
// output
|
||||
var diffableDataSource: UICollectionViewDiffableDataSource<SearchHistorySection, SearchHistoryItem>?
|
||||
|
||||
init(context: AppContext, authContext: AuthContext) {
|
||||
self.context = context
|
||||
self.authContext = authContext
|
||||
self.searchHistoryFetchedResultController = SearchHistoryFetchedResultController(managedObjectContext: context.managedObjectContext)
|
||||
|
||||
searchHistoryFetchedResultController.domain.value = authContext.mastodonAuthenticationBox.domain
|
||||
searchHistoryFetchedResultController.userID.value = authContext.mastodonAuthenticationBox.userID
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -6,13 +6,11 @@
|
|||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
import CoreDataStack
|
||||
import MastodonSDK
|
||||
|
||||
enum SearchResultItem: Hashable {
|
||||
case user(ManagedObjectRecord<MastodonUser>)
|
||||
case status(ManagedObjectRecord<Status>)
|
||||
case user(Mastodon.Entity.Account)
|
||||
case status(Mastodon.Entity.Status)
|
||||
case hashtag(tag: Mastodon.Entity.Tag)
|
||||
case bottomLoader(attribute: BottomLoaderAttribute)
|
||||
}
|
||||
|
|
|
@ -32,8 +32,8 @@ extension SearchResultViewModel {
|
|||
diffableDataSource.apply(snapshot, animatingDifferences: false)
|
||||
|
||||
Publishers.CombineLatest3(
|
||||
statusFetchedResultsController.$records,
|
||||
userFetchedResultsController.$records,
|
||||
$statusRecords,
|
||||
$userRecords,
|
||||
$hashtags
|
||||
)
|
||||
.map { statusRecords, userRecords, hashtags in
|
||||
|
|
|
@ -7,8 +7,6 @@
|
|||
|
||||
import Foundation
|
||||
import Combine
|
||||
import CoreData
|
||||
import CoreDataStack
|
||||
import GameplayKit
|
||||
import MastodonSDK
|
||||
import MastodonCore
|
||||
|
@ -22,8 +20,11 @@ final class SearchResultViewModel {
|
|||
let searchScope: SearchScope
|
||||
let searchText: String
|
||||
@Published var hashtags: [Mastodon.Entity.Tag] = []
|
||||
let userFetchedResultsController: UserFetchedResultsController
|
||||
let statusFetchedResultsController: StatusFetchedResultsController
|
||||
@Published var userRecords = [Mastodon.Entity.Account]()
|
||||
@Published var statusRecords = [Mastodon.Entity.Status]()
|
||||
|
||||
// let userFetchedResultsController: UserFetchedResultsController
|
||||
// let statusFetchedResultsController: StatusFetchedResultsController
|
||||
let listBatchFetchViewModel = ListBatchFetchViewModel()
|
||||
|
||||
var cellFrameCache = NSCache<NSNumber, NSValue>()
|
||||
|
@ -51,15 +52,15 @@ final class SearchResultViewModel {
|
|||
self.searchScope = searchScope
|
||||
self.searchText = searchText
|
||||
|
||||
self.userFetchedResultsController = UserFetchedResultsController(
|
||||
managedObjectContext: context.managedObjectContext,
|
||||
domain: authContext.mastodonAuthenticationBox.domain,
|
||||
additionalPredicate: nil
|
||||
)
|
||||
self.statusFetchedResultsController = StatusFetchedResultsController(
|
||||
managedObjectContext: context.managedObjectContext,
|
||||
domain: authContext.mastodonAuthenticationBox.domain,
|
||||
additionalTweetPredicate: nil
|
||||
)
|
||||
// self.userFetchedResultsController = UserFetchedResultsController(
|
||||
// managedObjectContext: context.managedObjectContext,
|
||||
// domain: authContext.mastodonAuthenticationBox.domain,
|
||||
// additionalPredicate: nil
|
||||
// )
|
||||
// self.statusFetchedResultsController = StatusFetchedResultsController(
|
||||
// managedObjectContext: context.managedObjectContext,
|
||||
// domain: authContext.mastodonAuthenticationBox.domain,
|
||||
// additionalTweetPredicate: nil
|
||||
// )
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,17 +8,16 @@
|
|||
import UIKit
|
||||
import Combine
|
||||
import MastodonUI
|
||||
import CoreDataStack
|
||||
import MetaTextKit
|
||||
import MastodonMeta
|
||||
import Meta
|
||||
import MastodonAsset
|
||||
import MastodonCore
|
||||
import MastodonLocalization
|
||||
import class CoreDataStack.Notification
|
||||
import MastodonSDK
|
||||
|
||||
extension NotificationView {
|
||||
public func configure(feed: Feed) {
|
||||
public func configure(feed: FeedItem) {
|
||||
guard let notification = feed.notification else {
|
||||
assertionFailure()
|
||||
return
|
||||
|
@ -29,17 +28,10 @@ extension NotificationView {
|
|||
}
|
||||
|
||||
extension NotificationView {
|
||||
public func configure(notification: Notification) {
|
||||
viewModel.objects.insert(notification)
|
||||
|
||||
public func configure(notification: Mastodon.Entity.Notification) {
|
||||
configureAuthor(notification: notification)
|
||||
|
||||
guard let type = MastodonNotificationType(rawValue: notification.typeRaw) else {
|
||||
assertionFailure()
|
||||
return
|
||||
}
|
||||
|
||||
switch type {
|
||||
|
||||
switch notification.type {
|
||||
case .follow:
|
||||
setAuthorContainerBottomPaddingViewDisplay()
|
||||
case .followRequest:
|
||||
|
@ -63,167 +55,125 @@ extension NotificationView {
|
|||
}
|
||||
|
||||
extension NotificationView {
|
||||
private func configureAuthor(notification: Notification) {
|
||||
private func configureAuthor(notification: Mastodon.Entity.Notification) {
|
||||
let author = notification.account
|
||||
// author avatar
|
||||
|
||||
Publishers.CombineLatest(
|
||||
author.publisher(for: \.avatar),
|
||||
UserDefaults.shared.publisher(for: \.preferredStaticAvatar)
|
||||
)
|
||||
.map { _ in author.avatarImageURL() }
|
||||
.assign(to: \.authorAvatarImageURL, on: viewModel)
|
||||
.store(in: &disposeBag)
|
||||
viewModel.authorAvatarImageURL = author.avatarImageURL()
|
||||
|
||||
// author name
|
||||
Publishers.CombineLatest(
|
||||
author.publisher(for: \.displayName),
|
||||
author.publisher(for: \.emojis)
|
||||
)
|
||||
.map { _, emojis in
|
||||
viewModel.authorName = {
|
||||
do {
|
||||
let content = MastodonContent(content: author.displayNameWithFallback, emojis: emojis.asDictionary)
|
||||
let content = MastodonContent(content: author.displayNameWithFallback, emojis: author.emojis?.asDictionary ?? [:])
|
||||
let metaContent = try MastodonMetaContent.convert(document: content)
|
||||
return metaContent
|
||||
} catch {
|
||||
assertionFailure(error.localizedDescription)
|
||||
return PlaintextMetaContent(string: author.displayNameWithFallback)
|
||||
}
|
||||
}
|
||||
.assign(to: \.authorName, on: viewModel)
|
||||
.store(in: &disposeBag)
|
||||
}()
|
||||
|
||||
// author username
|
||||
author.publisher(for: \.acct)
|
||||
.map { $0 as String? }
|
||||
.assign(to: \.authorUsername, on: viewModel)
|
||||
.store(in: &disposeBag)
|
||||
viewModel.authorUsername = author.acct
|
||||
|
||||
// timestamp
|
||||
viewModel.timestamp = notification.createAt
|
||||
viewModel.timestamp = notification.createdAt
|
||||
|
||||
viewModel.visibility = notification.status?.visibility ?? ._other("")
|
||||
viewModel.visibility = notification.status?.mastodonVisibility ?? ._other("")
|
||||
|
||||
// notification type indicator
|
||||
Publishers.CombineLatest3(
|
||||
notification.publisher(for: \.typeRaw),
|
||||
author.publisher(for: \.displayName),
|
||||
author.publisher(for: \.emojis)
|
||||
)
|
||||
.sink { [weak self] typeRaw, _, emojis in
|
||||
guard let self = self else { return }
|
||||
guard let type = MastodonNotificationType(rawValue: typeRaw) else {
|
||||
self.viewModel.notificationIndicatorText = nil
|
||||
return
|
||||
}
|
||||
self.viewModel.type = type
|
||||
self.viewModel.type = notification.type
|
||||
|
||||
func createMetaContent(text: String, emojis: MastodonContent.Emojis) -> MetaContent {
|
||||
let content = MastodonContent(content: text, emojis: emojis)
|
||||
guard let metaContent = try? MastodonMetaContent.convert(document: content) else {
|
||||
return PlaintextMetaContent(string: text)
|
||||
}
|
||||
return metaContent
|
||||
}
|
||||
|
||||
// TODO: fix the i18n. The subject should assert place at the string beginning
|
||||
switch type {
|
||||
case .follow:
|
||||
self.viewModel.notificationIndicatorText = createMetaContent(
|
||||
text: L10n.Scene.Notification.NotificationDescription.followedYou,
|
||||
emojis: emojis.asDictionary
|
||||
)
|
||||
case .followRequest:
|
||||
self.viewModel.notificationIndicatorText = createMetaContent(
|
||||
text: L10n.Scene.Notification.NotificationDescription.requestToFollowYou,
|
||||
emojis: emojis.asDictionary
|
||||
)
|
||||
case .mention:
|
||||
self.viewModel.notificationIndicatorText = createMetaContent(
|
||||
text: L10n.Scene.Notification.NotificationDescription.mentionedYou,
|
||||
emojis: emojis.asDictionary
|
||||
)
|
||||
case .reblog:
|
||||
self.viewModel.notificationIndicatorText = createMetaContent(
|
||||
text: L10n.Scene.Notification.NotificationDescription.rebloggedYourPost,
|
||||
emojis: emojis.asDictionary
|
||||
)
|
||||
case .favourite:
|
||||
self.viewModel.notificationIndicatorText = createMetaContent(
|
||||
text: L10n.Scene.Notification.NotificationDescription.favoritedYourPost,
|
||||
emojis: emojis.asDictionary
|
||||
)
|
||||
case .poll:
|
||||
self.viewModel.notificationIndicatorText = createMetaContent(
|
||||
text: L10n.Scene.Notification.NotificationDescription.pollHasEnded,
|
||||
emojis: emojis.asDictionary
|
||||
)
|
||||
case .status:
|
||||
self.viewModel.notificationIndicatorText = createMetaContent(
|
||||
text: .empty,
|
||||
emojis: emojis.asDictionary
|
||||
)
|
||||
case ._other:
|
||||
self.viewModel.notificationIndicatorText = nil
|
||||
func createMetaContent(text: String, emojis: MastodonContent.Emojis) -> MetaContent {
|
||||
let content = MastodonContent(content: text, emojis: emojis)
|
||||
guard let metaContent = try? MastodonMetaContent.convert(document: content) else {
|
||||
return PlaintextMetaContent(string: text)
|
||||
}
|
||||
return metaContent
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
|
||||
// TODO: fix the i18n. The subject should assert place at the string beginning
|
||||
guard let type = viewModel.type else { return }
|
||||
|
||||
let authContext = viewModel.authContext
|
||||
// isMuting
|
||||
author.publisher(for: \.mutingBy)
|
||||
.map { mutingBy in
|
||||
guard let authContext = authContext else { return false }
|
||||
return mutingBy.contains(where: {
|
||||
$0.id == authContext.mastodonAuthenticationBox.userID
|
||||
&& $0.domain == authContext.mastodonAuthenticationBox.domain
|
||||
})
|
||||
switch type {
|
||||
case .follow:
|
||||
self.viewModel.notificationIndicatorText = createMetaContent(
|
||||
text: L10n.Scene.Notification.NotificationDescription.followedYou,
|
||||
emojis: author.emojis?.asDictionary ?? [:]
|
||||
)
|
||||
case .followRequest:
|
||||
self.viewModel.notificationIndicatorText = createMetaContent(
|
||||
text: L10n.Scene.Notification.NotificationDescription.requestToFollowYou,
|
||||
emojis: author.emojis?.asDictionary ?? [:]
|
||||
)
|
||||
case .mention:
|
||||
self.viewModel.notificationIndicatorText = createMetaContent(
|
||||
text: L10n.Scene.Notification.NotificationDescription.mentionedYou,
|
||||
emojis: author.emojis?.asDictionary ?? [:]
|
||||
)
|
||||
case .reblog:
|
||||
self.viewModel.notificationIndicatorText = createMetaContent(
|
||||
text: L10n.Scene.Notification.NotificationDescription.rebloggedYourPost,
|
||||
emojis: author.emojis?.asDictionary ?? [:]
|
||||
)
|
||||
case .favourite:
|
||||
self.viewModel.notificationIndicatorText = createMetaContent(
|
||||
text: L10n.Scene.Notification.NotificationDescription.favoritedYourPost,
|
||||
emojis: author.emojis?.asDictionary ?? [:]
|
||||
)
|
||||
case .poll:
|
||||
self.viewModel.notificationIndicatorText = createMetaContent(
|
||||
text: L10n.Scene.Notification.NotificationDescription.pollHasEnded,
|
||||
emojis: author.emojis?.asDictionary ?? [:]
|
||||
)
|
||||
case .status:
|
||||
self.viewModel.notificationIndicatorText = createMetaContent(
|
||||
text: .empty,
|
||||
emojis: author.emojis?.asDictionary ?? [:]
|
||||
)
|
||||
case ._other:
|
||||
self.viewModel.notificationIndicatorText = nil
|
||||
}
|
||||
|
||||
guard let authContext = viewModel.authContext else { return }
|
||||
|
||||
Task {
|
||||
guard let context = viewModel.context else { return }
|
||||
if let relationship = try await context.apiService.relationship(records: [author], authenticationBox: authContext.mastodonAuthenticationBox).value.first {
|
||||
|
||||
viewModel.isMuting = relationship.muting == true
|
||||
viewModel.isBlocking = relationship.blockedBy == true // OR: blocking ???
|
||||
viewModel.isFollowed = relationship.followedBy
|
||||
}
|
||||
.assign(to: \.isMuting, on: viewModel)
|
||||
.store(in: &disposeBag)
|
||||
// isBlocking
|
||||
author.publisher(for: \.blockingBy)
|
||||
.map { blockingBy in
|
||||
guard let authContext = authContext else { return false }
|
||||
return blockingBy.contains(where: {
|
||||
$0.id == authContext.mastodonAuthenticationBox.userID
|
||||
&& $0.domain == authContext.mastodonAuthenticationBox.domain
|
||||
})
|
||||
}
|
||||
.assign(to: \.isBlocking, on: viewModel)
|
||||
.store(in: &disposeBag)
|
||||
|
||||
// let pendingFollowRequests = try await context.apiService.pendingFollowRequest(userID: notification.account.id, authenticationBox: authContext.mastodonAuthenticationBox).value
|
||||
|
||||
// pendingFollowRequests
|
||||
}
|
||||
|
||||
// isMyself
|
||||
Publishers.CombineLatest(
|
||||
author.publisher(for: \.domain),
|
||||
author.publisher(for: \.id)
|
||||
)
|
||||
.map { domain, id in
|
||||
guard let authContext = authContext else { return false }
|
||||
return authContext.mastodonAuthenticationBox.domain == domain
|
||||
&& authContext.mastodonAuthenticationBox.userID == id
|
||||
}
|
||||
.assign(to: \.isMyself, on: viewModel)
|
||||
.store(in: &disposeBag)
|
||||
viewModel.isMyself = (author.domain == authContext.mastodonAuthenticationBox.domain) && (author.id == authContext.mastodonAuthenticationBox.userID)
|
||||
|
||||
#warning("re-implemented the two below")
|
||||
// follow request state
|
||||
notification.publisher(for: \.followRequestState)
|
||||
.assign(to: \.followRequestState, on: viewModel)
|
||||
.store(in: &disposeBag)
|
||||
|
||||
notification.publisher(for: \.transientFollowRequestState)
|
||||
.assign(to: \.transientFollowRequestState, on: viewModel)
|
||||
.store(in: &disposeBag)
|
||||
// notification.publisher(for: \.followRequestState)
|
||||
// .assign(to: \.followRequestState, on: viewModel)
|
||||
// .store(in: &disposeBag)
|
||||
|
||||
// notification.publisher(for: \.transientFollowRequestState)
|
||||
// .assign(to: \.transientFollowRequestState, on: viewModel)
|
||||
// .store(in: &disposeBag)
|
||||
|
||||
// Following
|
||||
author.publisher(for: \.followingBy)
|
||||
.map { [weak viewModel] followingBy in
|
||||
guard let viewModel = viewModel else { return false }
|
||||
guard let authContext = viewModel.authContext else { return false }
|
||||
return followingBy.contains(where: {
|
||||
$0.id == authContext.mastodonAuthenticationBox.userID && $0.domain == authContext.mastodonAuthenticationBox.domain
|
||||
})
|
||||
}
|
||||
.assign(to: \.isFollowed, on: viewModel)
|
||||
.store(in: &disposeBag)
|
||||
// author.publisher(for: \.followingBy)
|
||||
// .map { [weak viewModel] followingBy in
|
||||
// guard let viewModel = viewModel else { return false }
|
||||
// guard let authContext = viewModel.authContext else { return false }
|
||||
// return followingBy.contains(where: {
|
||||
// $0.id == authContext.mastodonAuthenticationBox.userID && $0.domain == authContext.mastodonAuthenticationBox.domain
|
||||
// })
|
||||
// }
|
||||
// .assign(to: \.isFollowed, on: viewModel)
|
||||
// .store(in: &disposeBag)
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,94 +7,82 @@
|
|||
|
||||
import UIKit
|
||||
import Combine
|
||||
import CoreDataStack
|
||||
import MetaTextKit
|
||||
import MastodonCore
|
||||
import MastodonUI
|
||||
import MastodonSDK
|
||||
|
||||
extension PollOptionView {
|
||||
public func configure(pollOption option: PollOption) {
|
||||
guard let poll = option.poll, let status = poll.status else {
|
||||
assertionFailure("PollOption to be configured is expected to be part of Poll with Status")
|
||||
return
|
||||
}
|
||||
public func configure(status: Mastodon.Entity.Status, pollOption option: Mastodon.Entity.Poll.Option, poll: Mastodon.Entity.Poll) {
|
||||
|
||||
viewModel.objects.insert(option)
|
||||
|
||||
// metaContent
|
||||
option.publisher(for: \.title)
|
||||
.map { title -> MetaContent? in
|
||||
return PlaintextMetaContent(string: title)
|
||||
}
|
||||
.assign(to: \.metaContent, on: viewModel)
|
||||
.store(in: &disposeBag)
|
||||
viewModel.metaContent = PlaintextMetaContent(string: option.title)
|
||||
|
||||
// percentage
|
||||
Publishers.CombineLatest(
|
||||
poll.publisher(for: \.votersCount),
|
||||
option.publisher(for: \.votesCount)
|
||||
)
|
||||
.map { pollVotersCount, optionVotesCount -> Double? in
|
||||
viewModel.percentage = {
|
||||
let pollVotersCount = poll.votersCount ?? 0
|
||||
let optionVotesCount = option.votesCount ?? 0
|
||||
guard pollVotersCount > 0, optionVotesCount >= 0 else { return 0 }
|
||||
return Double(optionVotesCount) / Double(pollVotersCount)
|
||||
}
|
||||
.assign(to: \.percentage, on: viewModel)
|
||||
.store(in: &disposeBag)
|
||||
}()
|
||||
|
||||
// $isExpire
|
||||
poll.publisher(for: \.expired)
|
||||
.assign(to: \.isExpire, on: viewModel)
|
||||
.store(in: &disposeBag)
|
||||
viewModel.isExpire = poll.expired
|
||||
|
||||
// isMultiple
|
||||
viewModel.isMultiple = poll.multiple
|
||||
|
||||
let optionIndex = option.index
|
||||
let authorDomain = status.author.domain
|
||||
let authorID = status.author.id
|
||||
// let optionIndex = option.index
|
||||
let authorDomain = status.account.domain
|
||||
let authorID = status.account.id
|
||||
|
||||
#warning("re-implemented the code below")
|
||||
// isSelect, isPollVoted, isMyPoll
|
||||
Publishers.CombineLatest4(
|
||||
option.publisher(for: \.poll),
|
||||
option.publisher(for: \.votedBy),
|
||||
option.publisher(for: \.isSelected),
|
||||
viewModel.$authContext
|
||||
)
|
||||
.sink { [weak self] poll, optionVotedBy, isSelected, authContext in
|
||||
guard let self = self, let poll = poll else { return }
|
||||
|
||||
let domain = authContext?.mastodonAuthenticationBox.domain ?? ""
|
||||
let userID = authContext?.mastodonAuthenticationBox.userID ?? ""
|
||||
|
||||
let options = poll.options
|
||||
let pollVoteBy = poll.votedBy ?? Set()
|
||||
|
||||
let isMyPoll = authorDomain == domain
|
||||
&& authorID == userID
|
||||
|
||||
let votedOptions = options.filter { option in
|
||||
let votedBy = option.votedBy ?? Set()
|
||||
return votedBy.contains(where: { $0.id == userID && $0.domain == domain })
|
||||
}
|
||||
let isRemoteVotedOption = votedOptions.contains(where: { $0.index == optionIndex })
|
||||
let isRemoteVotedPoll = pollVoteBy.contains(where: { $0.id == userID && $0.domain == domain })
|
||||
|
||||
let isLocalVotedOption = isSelected
|
||||
|
||||
let isSelect: Bool? = {
|
||||
if isLocalVotedOption {
|
||||
return true
|
||||
} else if !votedOptions.isEmpty {
|
||||
return isRemoteVotedOption ? true : false
|
||||
} else if isRemoteVotedPoll, votedOptions.isEmpty {
|
||||
// the poll voted. But server not mark voted options
|
||||
return nil
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}()
|
||||
self.viewModel.isSelect = isSelect
|
||||
self.viewModel.isPollVoted = isRemoteVotedPoll
|
||||
self.viewModel.isMyPoll = isMyPoll
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
// Publishers.CombineLatest4(
|
||||
// option.publisher(for: \.poll),
|
||||
// option.publisher(for: \.votedBy),
|
||||
// option.publisher(for: \.isSelected),
|
||||
// viewModel.$authContext
|
||||
// )
|
||||
// .sink { [weak self] poll, optionVotedBy, isSelected, authContext in
|
||||
// guard let self = self, let poll = poll else { return }
|
||||
//
|
||||
// let domain = authContext?.mastodonAuthenticationBox.domain ?? ""
|
||||
// let userID = authContext?.mastodonAuthenticationBox.userID ?? ""
|
||||
//
|
||||
// let options = poll.options
|
||||
// let pollVoteBy = poll.votedBy ?? Set()
|
||||
//
|
||||
// let isMyPoll = authorDomain == domain
|
||||
// && authorID == userID
|
||||
//
|
||||
// let votedOptions = options.filter { option in
|
||||
// let votedBy = option.votedBy ?? Set()
|
||||
// return votedBy.contains(where: { $0.id == userID && $0.domain == domain })
|
||||
// }
|
||||
// let isRemoteVotedOption = votedOptions.contains(where: { $0.index == optionIndex })
|
||||
// let isRemoteVotedPoll = pollVoteBy.contains(where: { $0.id == userID && $0.domain == domain })
|
||||
//
|
||||
// let isLocalVotedOption = isSelected
|
||||
//
|
||||
// let isSelect: Bool? = {
|
||||
// if isLocalVotedOption {
|
||||
// return true
|
||||
// } else if !votedOptions.isEmpty {
|
||||
// return isRemoteVotedOption ? true : false
|
||||
// } else if isRemoteVotedPoll, votedOptions.isEmpty {
|
||||
// // the poll voted. But server not mark voted options
|
||||
// return nil
|
||||
// } else {
|
||||
// return false
|
||||
// }
|
||||
// }()
|
||||
// self.viewModel.isSelect = isSelect
|
||||
// self.viewModel.isPollVoted = isRemoteVotedPoll
|
||||
// self.viewModel.isMyPoll = isMyPoll
|
||||
// }
|
||||
// .store(in: &disposeBag)
|
||||
|
||||
// appearance
|
||||
checkmarkBackgroundView.backgroundColor = UIColor(dynamicProvider: { trailtCollection in
|
||||
return trailtCollection.userInterfaceStyle == .light ? .white : SystemTheme.tableViewCellSelectionBackgroundColor
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
import UIKit
|
||||
import Combine
|
||||
import MastodonUI
|
||||
import CoreDataStack
|
||||
import MastodonLocalization
|
||||
import MastodonMeta
|
||||
import MastodonCore
|
||||
|
@ -17,55 +16,32 @@ import MastodonSDK
|
|||
import MastodonAsset
|
||||
|
||||
extension UserView {
|
||||
public func configure(user: MastodonUser, delegate: UserViewDelegate?) {
|
||||
public func configure(user: Mastodon.Entity.Account, delegate: UserViewDelegate?) {
|
||||
self.delegate = delegate
|
||||
viewModel.user = user
|
||||
viewModel.account = nil
|
||||
viewModel.relationship = nil
|
||||
|
||||
Publishers.CombineLatest(
|
||||
user.publisher(for: \.avatar),
|
||||
UserDefaults.shared.publisher(for: \.preferredStaticAvatar)
|
||||
)
|
||||
.map { _ in user.avatarImageURL() }
|
||||
.assign(to: \.authorAvatarImageURL, on: viewModel)
|
||||
.store(in: &disposeBag)
|
||||
viewModel.authorAvatarImageURL = user.avatarImageURL()
|
||||
|
||||
// author name
|
||||
Publishers.CombineLatest(
|
||||
user.publisher(for: \.displayName),
|
||||
user.publisher(for: \.emojis)
|
||||
)
|
||||
.map { _, emojis in
|
||||
viewModel.authorName = {
|
||||
do {
|
||||
let content = MastodonContent(content: user.displayNameWithFallback, emojis: emojis.asDictionary)
|
||||
let content = MastodonContent(content: user.displayNameWithFallback, emojis: user.emojis?.asDictionary ?? [:])
|
||||
let metaContent = try MastodonMetaContent.convert(document: content)
|
||||
return metaContent
|
||||
} catch {
|
||||
assertionFailure(error.localizedDescription)
|
||||
return PlaintextMetaContent(string: user.displayNameWithFallback)
|
||||
}
|
||||
}
|
||||
.assign(to: \.authorName, on: viewModel)
|
||||
.store(in: &disposeBag)
|
||||
}()
|
||||
|
||||
// author username
|
||||
user.publisher(for: \.acct)
|
||||
.map { $0 as String? }
|
||||
.assign(to: \.authorUsername, on: viewModel)
|
||||
.store(in: &disposeBag)
|
||||
viewModel.authorUsername = user.acct
|
||||
|
||||
user.publisher(for: \.followersCount)
|
||||
.map { Int($0) }
|
||||
.assign(to: \.authorFollowers, on: viewModel)
|
||||
.store(in: &disposeBag)
|
||||
viewModel.authorFollowers = user.followersCount
|
||||
|
||||
user.publisher(for: \.fields)
|
||||
.map { fields in
|
||||
let firstVerified = fields.first(where: { $0.verifiedAt != nil })
|
||||
return firstVerified?.value
|
||||
}
|
||||
.assign(to: \.authorVerifiedLink, on: viewModel)
|
||||
.store(in: &disposeBag)
|
||||
viewModel.authorVerifiedLink = user.fields?.first(where: { $0.verifiedAt != nil })?.value
|
||||
}
|
||||
|
||||
func configure(with account: Mastodon.Entity.Account, relationship: Mastodon.Entity.Relationship?, delegate: UserViewDelegate?) {
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
//
|
||||
|
||||
import UIKit
|
||||
import CoreDataStack
|
||||
import MastodonSDK
|
||||
|
||||
extension StatusTableViewCell {
|
||||
final class ViewModel {
|
||||
|
@ -17,8 +17,8 @@ extension StatusTableViewCell {
|
|||
}
|
||||
|
||||
enum Value {
|
||||
case feed(Feed)
|
||||
case status(Status)
|
||||
case feed(FeedItem)
|
||||
case status(Mastodon.Entity.Status)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -38,13 +38,8 @@ extension StatusTableViewCell {
|
|||
switch viewModel.value {
|
||||
case .feed(let feed):
|
||||
statusView.configure(feed: feed)
|
||||
|
||||
feed.publisher(for: \.hasMore)
|
||||
.sink { [weak self] hasMore in
|
||||
guard let self = self else { return }
|
||||
self.separatorLine.isHidden = hasMore
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
|
||||
self.separatorLine.isHidden = feed.hasMore
|
||||
|
||||
case .status(let status):
|
||||
statusView.configure(status: status)
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
//
|
||||
|
||||
import UIKit
|
||||
import CoreDataStack
|
||||
import MastodonSDK
|
||||
|
||||
extension StatusThreadRootTableViewCell {
|
||||
final class ViewModel {
|
||||
|
@ -17,7 +17,7 @@ extension StatusThreadRootTableViewCell {
|
|||
}
|
||||
|
||||
enum Value {
|
||||
case status(Status)
|
||||
case status(Mastodon.Entity.Status)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
//
|
||||
|
||||
import UIKit
|
||||
import CoreDataStack
|
||||
import MastodonUI
|
||||
import Combine
|
||||
import MastodonCore
|
||||
|
@ -14,13 +13,13 @@ import MastodonSDK
|
|||
|
||||
extension UserTableViewCell {
|
||||
final class ViewModel {
|
||||
let user: MastodonUser
|
||||
let user: Mastodon.Entity.Account
|
||||
|
||||
let followedUsers: AnyPublisher<[String], Never>
|
||||
let blockedUsers: AnyPublisher<[String], Never>
|
||||
let followRequestedUsers: AnyPublisher<[String], Never>
|
||||
|
||||
init(user: MastodonUser, followedUsers: AnyPublisher<[String], Never>, blockedUsers: AnyPublisher<[String], Never>, followRequestedUsers: AnyPublisher<[String], Never>) {
|
||||
init(user: Mastodon.Entity.Account, followedUsers: AnyPublisher<[String], Never>, blockedUsers: AnyPublisher<[String], Never>, followRequestedUsers: AnyPublisher<[String], Never>) {
|
||||
self.user = user
|
||||
self.followedUsers = followedUsers
|
||||
self.followRequestedUsers = followRequestedUsers
|
||||
|
@ -32,7 +31,7 @@ extension UserTableViewCell {
|
|||
extension UserTableViewCell {
|
||||
|
||||
func configure(
|
||||
me: MastodonUser,
|
||||
me: Mastodon.Entity.Account,
|
||||
tableView: UITableView,
|
||||
account: Mastodon.Entity.Account,
|
||||
relationship: Mastodon.Entity.Relationship?,
|
||||
|
@ -47,61 +46,62 @@ extension UserTableViewCell {
|
|||
}
|
||||
|
||||
func configure(
|
||||
me: MastodonUser? = nil,
|
||||
me: Mastodon.Entity.Account? = nil,
|
||||
tableView: UITableView,
|
||||
viewModel: ViewModel,
|
||||
delegate: UserTableViewCellDelegate?
|
||||
) {
|
||||
userView.configure(user: viewModel.user, delegate: delegate)
|
||||
|
||||
guard let me = me else {
|
||||
guard let me else {
|
||||
return userView.setButtonState(.none)
|
||||
}
|
||||
|
||||
if viewModel.user == me {
|
||||
userView.setButtonState(.none)
|
||||
} else {
|
||||
userView.setButtonState(.loading)
|
||||
}
|
||||
|
||||
Publishers.CombineLatest3(
|
||||
viewModel.followedUsers,
|
||||
viewModel.followRequestedUsers,
|
||||
viewModel.blockedUsers
|
||||
)
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] followed, requested, blocked in
|
||||
if viewModel.user == me {
|
||||
self?.userView.setButtonState(.none)
|
||||
} else if blocked.contains(viewModel.user.id) {
|
||||
self?.userView.setButtonState(.blocked)
|
||||
} else if followed.contains(viewModel.user.id) {
|
||||
self?.userView.setButtonState(.unfollow)
|
||||
} else if requested.contains(viewModel.user.id) {
|
||||
self?.userView.setButtonState(.pending)
|
||||
} else if viewModel.user.locked {
|
||||
self?.userView.setButtonState(.request)
|
||||
} else if viewModel.user != me {
|
||||
self?.userView.setButtonState(.follow)
|
||||
}
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
|
||||
#warning("re-implement the code below")
|
||||
// if viewModel.user == me {
|
||||
// userView.setButtonState(.none)
|
||||
// } else {
|
||||
// userView.setButtonState(.loading)
|
||||
// }
|
||||
//
|
||||
// Publishers.CombineLatest3(
|
||||
// viewModel.followedUsers,
|
||||
// viewModel.followRequestedUsers,
|
||||
// viewModel.blockedUsers
|
||||
// )
|
||||
// .receive(on: DispatchQueue.main)
|
||||
// .sink { [weak self] followed, requested, blocked in
|
||||
// if viewModel.user == me {
|
||||
// self?.userView.setButtonState(.none)
|
||||
// } else if blocked.contains(viewModel.user.id) {
|
||||
// self?.userView.setButtonState(.blocked)
|
||||
// } else if followed.contains(viewModel.user.id) {
|
||||
// self?.userView.setButtonState(.unfollow)
|
||||
// } else if requested.contains(viewModel.user.id) {
|
||||
// self?.userView.setButtonState(.pending)
|
||||
// } else if viewModel.user.locked {
|
||||
// self?.userView.setButtonState(.request)
|
||||
// } else if viewModel.user != me {
|
||||
// self?.userView.setButtonState(.follow)
|
||||
// }
|
||||
// }
|
||||
// .store(in: &disposeBag)
|
||||
|
||||
self.delegate = delegate
|
||||
}
|
||||
}
|
||||
|
||||
extension UserTableViewCellDelegate where Self: NeedsDependency & AuthContextProvider {
|
||||
func userView(_ view: UserView, didTapButtonWith state: UserView.ButtonState, for user: MastodonUser) {
|
||||
func userView(_ view: UserView, didTapButtonWith state: UserView.ButtonState, for user: Mastodon.Entity.Account) {
|
||||
Task {
|
||||
try await DataSourceFacade.responseToUserViewButtonAction(
|
||||
dependency: self,
|
||||
user: user.asRecord,
|
||||
user: user,
|
||||
buttonState: state
|
||||
)
|
||||
}
|
||||
}
|
||||
func userView(_ view: UserView, didTapButtonWith state: UserView.ButtonState, for account: Mastodon.Entity.Account, me: MastodonUser?) {
|
||||
func userView(_ view: UserView, didTapButtonWith state: UserView.ButtonState, for account: Mastodon.Entity.Account, me: Mastodon.Entity.Account?) {
|
||||
Task {
|
||||
await MainActor.run { view.setButtonState(.loading) }
|
||||
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
|
||||
import UIKit
|
||||
import Combine
|
||||
import CoreDataStack
|
||||
import MastodonAsset
|
||||
import MastodonLocalization
|
||||
import MastodonUI
|
||||
|
|
|
@ -6,8 +6,8 @@
|
|||
//
|
||||
|
||||
import Foundation
|
||||
import CoreDataStack
|
||||
import MastodonSDK
|
||||
|
||||
enum RecommendAccountItem: Hashable {
|
||||
case account(ManagedObjectRecord<MastodonUser>)
|
||||
case account(Mastodon.Entity.Account)
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue