chore: [WIP] inject AuthContext into ViewModel
This commit is contained in:
parent
f73241caee
commit
bb5c999bea
|
@ -7,6 +7,5 @@ set -eo pipefail
|
|||
|
||||
xcodebuild -workspace Mastodon.xcworkspace \
|
||||
-scheme Mastodon \
|
||||
-destination "platform=iOS Simulator,name=iPhone SE (2nd generation)" \
|
||||
clean \
|
||||
build | xcpretty
|
||||
|
|
|
@ -4109,7 +4109,7 @@
|
|||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0.7;
|
||||
MARKETING_VERSION = 1.4.5;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
|
@ -4139,7 +4139,7 @@
|
|||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0.7;
|
||||
MARKETING_VERSION = 1.4.5;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
|
@ -4312,7 +4312,7 @@
|
|||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0.7;
|
||||
MARKETING_VERSION = 1.4.5;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
|
@ -4609,7 +4609,7 @@
|
|||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0.7;
|
||||
MARKETING_VERSION = 1.4.5;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
|
|
|
@ -112,12 +112,12 @@
|
|||
<key>NotificationService.xcscheme_^#shared#^_</key>
|
||||
<dict>
|
||||
<key>orderHint</key>
|
||||
<integer>7</integer>
|
||||
<integer>25</integer>
|
||||
</dict>
|
||||
<key>ShareActionExtension.xcscheme_^#shared#^_</key>
|
||||
<dict>
|
||||
<key>orderHint</key>
|
||||
<integer>6</integer>
|
||||
<integer>24</integer>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>SuppressBuildableAutocreation</key>
|
||||
|
|
|
@ -1,241 +1,239 @@
|
|||
{
|
||||
"object": {
|
||||
"pins" : [
|
||||
{
|
||||
"package": "Alamofire",
|
||||
"repositoryURL": "https://github.com/Alamofire/Alamofire.git",
|
||||
"identity" : "alamofire",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/Alamofire/Alamofire.git",
|
||||
"state" : {
|
||||
"branch": null,
|
||||
"revision" : "354dda32d89fc8cd4f5c46487f64957d355f53d8",
|
||||
"version" : "5.6.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "AlamofireImage",
|
||||
"repositoryURL": "https://github.com/Alamofire/AlamofireImage.git",
|
||||
"identity" : "alamofireimage",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/Alamofire/AlamofireImage.git",
|
||||
"state" : {
|
||||
"branch": null,
|
||||
"revision" : "98cbb00ce0ec5fc8e52a5b50a6bfc08d3e5aee10",
|
||||
"version" : "4.2.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "CommonOSLog",
|
||||
"repositoryURL": "https://github.com/MainasuK/CommonOSLog",
|
||||
"identity" : "commonoslog",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/MainasuK/CommonOSLog",
|
||||
"state" : {
|
||||
"branch": null,
|
||||
"revision" : "c121624a30698e9886efe38aebb36ff51c01b6c2",
|
||||
"version" : "0.1.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "FaviconFinder",
|
||||
"repositoryURL": "https://github.com/will-lumley/FaviconFinder.git",
|
||||
"identity" : "faviconfinder",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/will-lumley/FaviconFinder.git",
|
||||
"state" : {
|
||||
"branch": null,
|
||||
"revision" : "1f74844f77f79b95c0bb0130b3a87d4f340e6d3a",
|
||||
"version" : "3.3.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "FLAnimatedImage",
|
||||
"repositoryURL": "https://github.com/Flipboard/FLAnimatedImage.git",
|
||||
"identity" : "flanimatedimage",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/Flipboard/FLAnimatedImage.git",
|
||||
"state" : {
|
||||
"branch": null,
|
||||
"revision" : "e7f9fd4681ae41bf6f3056db08af4f401d61da52",
|
||||
"version" : "1.0.16"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "FPSIndicator",
|
||||
"repositoryURL": "https://github.com/MainasuK/FPSIndicator.git",
|
||||
"identity" : "fpsindicator",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/MainasuK/FPSIndicator.git",
|
||||
"state" : {
|
||||
"branch": null,
|
||||
"revision" : "e4a5067ccd5293b024c767f09e51056afd4a4796",
|
||||
"version" : "1.1.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "Fuzi",
|
||||
"repositoryURL": "https://github.com/cezheng/Fuzi.git",
|
||||
"identity" : "fuzi",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/cezheng/Fuzi.git",
|
||||
"state" : {
|
||||
"branch": null,
|
||||
"revision" : "f08c8323da21e985f3772610753bcfc652c2103f",
|
||||
"version" : "3.1.3"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "KeychainAccess",
|
||||
"repositoryURL": "https://github.com/kishikawakatsumi/KeychainAccess.git",
|
||||
"identity" : "keychainaccess",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/kishikawakatsumi/KeychainAccess.git",
|
||||
"state" : {
|
||||
"branch": null,
|
||||
"revision" : "84e546727d66f1adc5439debad16270d0fdd04e7",
|
||||
"version" : "4.2.2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "MetaTextKit",
|
||||
"repositoryURL": "https://github.com/TwidereProject/MetaTextKit.git",
|
||||
"identity" : "metatextkit",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/TwidereProject/MetaTextKit.git",
|
||||
"state" : {
|
||||
"branch": null,
|
||||
"revision" : "dcd5255d6930c2fab408dc8562c577547e477624",
|
||||
"version" : "2.2.5"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "Nuke",
|
||||
"repositoryURL": "https://github.com/kean/Nuke.git",
|
||||
"identity" : "nuke",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/kean/Nuke.git",
|
||||
"state" : {
|
||||
"branch": null,
|
||||
"revision" : "0ea7545b5c918285aacc044dc75048625c8257cc",
|
||||
"version" : "10.8.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "NukeFLAnimatedImagePlugin",
|
||||
"repositoryURL": "https://github.com/kean/Nuke-FLAnimatedImage-Plugin.git",
|
||||
"identity" : "nuke-flanimatedimage-plugin",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/kean/Nuke-FLAnimatedImage-Plugin.git",
|
||||
"state" : {
|
||||
"branch": null,
|
||||
"revision" : "b59c346a7d536336db3b0f12c72c6e53ee709e16",
|
||||
"version" : "8.0.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "Pageboy",
|
||||
"repositoryURL": "https://github.com/uias/Pageboy",
|
||||
"identity" : "pageboy",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/uias/Pageboy",
|
||||
"state" : {
|
||||
"branch": null,
|
||||
"revision" : "34ecb6e7c4e0e07494960ab2f7cc9a02293915a6",
|
||||
"version" : "3.6.2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "PanModal",
|
||||
"repositoryURL": "https://github.com/slackhq/PanModal.git",
|
||||
"identity" : "panmodal",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/slackhq/PanModal.git",
|
||||
"state" : {
|
||||
"branch": null,
|
||||
"revision" : "b012aecb6b67a8e46369227f893c12544846613f",
|
||||
"version" : "1.2.7"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "SDWebImage",
|
||||
"repositoryURL": "https://github.com/SDWebImage/SDWebImage.git",
|
||||
"identity" : "sdwebimage",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/SDWebImage/SDWebImage.git",
|
||||
"state" : {
|
||||
"branch": null,
|
||||
"revision" : "2e63d0061da449ad0ed130768d05dceb1496de44",
|
||||
"version" : "5.12.5"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "swift-collections",
|
||||
"repositoryURL": "https://github.com/apple/swift-collections.git",
|
||||
"identity" : "swift-collections",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/apple/swift-collections.git",
|
||||
"state" : {
|
||||
"branch": null,
|
||||
"revision" : "f504716c27d2e5d4144fa4794b12129301d17729",
|
||||
"version" : "1.0.3"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "swift-nio",
|
||||
"repositoryURL": "https://github.com/apple/swift-nio.git",
|
||||
"identity" : "swift-nio",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/apple/swift-nio.git",
|
||||
"state" : {
|
||||
"branch": null,
|
||||
"revision" : "546610d52b19be3e19935e0880bb06b9c03f5cef",
|
||||
"version" : "1.14.4"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "swift-nio-zlib-support",
|
||||
"repositoryURL": "https://github.com/apple/swift-nio-zlib-support.git",
|
||||
"identity" : "swift-nio-zlib-support",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/apple/swift-nio-zlib-support.git",
|
||||
"state" : {
|
||||
"branch": null,
|
||||
"revision" : "37760e9a52030bb9011972c5213c3350fa9d41fd",
|
||||
"version" : "1.0.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "SwiftSoup",
|
||||
"repositoryURL": "https://github.com/scinfu/SwiftSoup.git",
|
||||
"identity" : "swiftsoup",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/scinfu/SwiftSoup.git",
|
||||
"state" : {
|
||||
"branch": null,
|
||||
"revision" : "41e7c263fb8c277e980ebcb9b0b5f6031d3d4886",
|
||||
"version" : "2.4.2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "Introspect",
|
||||
"repositoryURL": "https://github.com/siteline/SwiftUI-Introspect.git",
|
||||
"identity" : "swiftui-introspect",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/siteline/SwiftUI-Introspect.git",
|
||||
"state" : {
|
||||
"branch": null,
|
||||
"revision" : "f2616860a41f9d9932da412a8978fec79c06fe24",
|
||||
"version" : "0.1.4"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "SwiftyJSON",
|
||||
"repositoryURL": "https://github.com/SwiftyJSON/SwiftyJSON.git",
|
||||
"identity" : "swiftyjson",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/SwiftyJSON/SwiftyJSON.git",
|
||||
"state" : {
|
||||
"branch": null,
|
||||
"revision" : "b3dcd7dbd0d488e1a7077cb33b00f2083e382f07",
|
||||
"version" : "5.0.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "TabBarPager",
|
||||
"repositoryURL": "https://github.com/TwidereProject/TabBarPager.git",
|
||||
"identity" : "tabbarpager",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/TwidereProject/TabBarPager.git",
|
||||
"state" : {
|
||||
"branch": null,
|
||||
"revision" : "488aa66d157a648901b61721212c0dec23d27ee5",
|
||||
"version" : "0.1.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "Tabman",
|
||||
"repositoryURL": "https://github.com/uias/Tabman",
|
||||
"identity" : "tabman",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/uias/Tabman",
|
||||
"state" : {
|
||||
"branch": null,
|
||||
"revision" : "4a4f7c755b875ffd4f9ef10d67a67883669d2465",
|
||||
"version" : "2.13.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "ThirdPartyMailer",
|
||||
"repositoryURL": "https://github.com/vtourraine/ThirdPartyMailer.git",
|
||||
"identity" : "thirdpartymailer",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/vtourraine/ThirdPartyMailer.git",
|
||||
"state" : {
|
||||
"branch": null,
|
||||
"revision" : "44c1cfaa6969963f22691aa67f88a69e3b6d651f",
|
||||
"version" : "2.1.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "TOCropViewController",
|
||||
"repositoryURL": "https://github.com/TimOliver/TOCropViewController.git",
|
||||
"identity" : "tocropviewcontroller",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/TimOliver/TOCropViewController.git",
|
||||
"state" : {
|
||||
"branch": null,
|
||||
"revision" : "d0470491f56e734731bbf77991944c0dfdee3e0e",
|
||||
"version" : "2.6.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "UIHostingConfigurationBackport",
|
||||
"repositoryURL": "https://github.com/woxtu/UIHostingConfigurationBackport.git",
|
||||
"identity" : "uihostingconfigurationbackport",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/woxtu/UIHostingConfigurationBackport.git",
|
||||
"state" : {
|
||||
"branch": null,
|
||||
"revision" : "6091f2d38faa4b24fc2ca0389c651e2f666624a3",
|
||||
"version" : "0.1.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "UITextView+Placeholder",
|
||||
"repositoryURL": "https://github.com/MainasuK/UITextView-Placeholder.git",
|
||||
"identity" : "uitextview-placeholder",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/MainasuK/UITextView-Placeholder.git",
|
||||
"state" : {
|
||||
"branch": null,
|
||||
"revision" : "20f513ded04a040cdf5467f0891849b1763ede3b",
|
||||
"version" : "1.4.1"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"version": 1
|
||||
],
|
||||
"version" : 2
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@ final public class SceneCoordinator {
|
|||
private weak var sceneDelegate: SceneDelegate!
|
||||
private weak var appContext: AppContext!
|
||||
|
||||
private var authContext: AuthContext?
|
||||
private(set) var authContext: AuthContext?
|
||||
|
||||
let id = UUID().uuidString
|
||||
|
||||
|
@ -45,17 +45,14 @@ final public class SceneCoordinator {
|
|||
|
||||
appContext.notificationService.requestRevealNotificationPublisher
|
||||
.receive(on: DispatchQueue.main)
|
||||
.compactMap { [weak self] pushNotification -> AnyPublisher<MastodonPushNotification?, Never> in
|
||||
guard let self = self else { return Just(nil).eraseToAnyPublisher() }
|
||||
// skip if no available account
|
||||
guard let currentActiveAuthenticationBox = appContext.authenticationService.activeMastodonAuthenticationBox.value else {
|
||||
return Just(nil).eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
.sink(receiveValue: { [weak self] pushNotification in
|
||||
guard let self = self else { return }
|
||||
Task {
|
||||
guard let currentActiveAuthenticationBox = self.authContext?.mastodonAuthenticationBox else { return }
|
||||
let accessToken = pushNotification.accessToken // use raw accessToken value without normalize
|
||||
if currentActiveAuthenticationBox.userAuthorization.accessToken == accessToken {
|
||||
// do nothing if notification for current account
|
||||
return Just(pushNotification).eraseToAnyPublisher()
|
||||
return
|
||||
} else {
|
||||
// switch to notification's account
|
||||
let request = MastodonAuthentication.sortedFetchRequest
|
||||
|
@ -64,41 +61,19 @@ final public class SceneCoordinator {
|
|||
request.fetchLimit = 1
|
||||
do {
|
||||
guard let authentication = try appContext.managedObjectContext.fetch(request).first else {
|
||||
return Just(nil).eraseToAnyPublisher()
|
||||
return
|
||||
}
|
||||
let domain = authentication.domain
|
||||
let userID = authentication.userID
|
||||
return appContext.authenticationService.activeMastodonUser(domain: domain, userID: userID)
|
||||
.receive(on: DispatchQueue.main)
|
||||
.map { [weak self] result -> MastodonPushNotification? in
|
||||
guard let self = self else { return nil }
|
||||
switch result {
|
||||
case .success:
|
||||
// reset view hierarchy
|
||||
let isSuccess = try await appContext.authenticationService.activeMastodonUser(domain: domain, userID: userID)
|
||||
guard isSuccess else { return }
|
||||
|
||||
self.setup()
|
||||
return pushNotification
|
||||
case .failure:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
.delay(for: 1, scheduler: DispatchQueue.main) // set delay to slow transition (not must)
|
||||
.eraseToAnyPublisher()
|
||||
} catch {
|
||||
assertionFailure(error.localizedDescription)
|
||||
return Just(nil).eraseToAnyPublisher()
|
||||
}
|
||||
}
|
||||
}
|
||||
.switchToLatest()
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] pushNotification in
|
||||
guard let self = self else { return }
|
||||
guard let pushNotification = pushNotification else { return }
|
||||
try await Task.sleep(nanoseconds: .second * 1)
|
||||
|
||||
// redirect to notification tab
|
||||
self.switchToTabBar(tab: .notification)
|
||||
|
||||
|
||||
// Delay in next run loop
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
guard let self = self else { return }
|
||||
|
@ -121,24 +96,32 @@ final public class SceneCoordinator {
|
|||
|
||||
// show notification related content
|
||||
guard let type = Mastodon.Entity.Notification.NotificationType(rawValue: pushNotification.notificationType) else { return }
|
||||
guard let authContext = self.authContext else { return }
|
||||
let notificationID = String(pushNotification.notificationID)
|
||||
|
||||
switch type {
|
||||
case .follow:
|
||||
let profileViewModel = RemoteProfileViewModel(context: appContext, notificationID: notificationID)
|
||||
self.present(scene: .profile(viewModel: profileViewModel), from: from, transition: .show)
|
||||
let profileViewModel = RemoteProfileViewModel(context: appContext, authContext: authContext, notificationID: notificationID)
|
||||
_ = self.present(scene: .profile(viewModel: profileViewModel), from: from, transition: .show)
|
||||
case .followRequest:
|
||||
// do nothing
|
||||
break
|
||||
case .mention, .reblog, .favourite, .poll, .status:
|
||||
let threadViewModel = RemoteThreadViewModel(context: appContext, notificationID: notificationID)
|
||||
self.present(scene: .thread(viewModel: threadViewModel), from: from, transition: .show)
|
||||
let threadViewModel = RemoteThreadViewModel(context: appContext, authContext: authContext, notificationID: notificationID)
|
||||
_ = self.present(scene: .thread(viewModel: threadViewModel), from: from, transition: .show)
|
||||
case ._other:
|
||||
assertionFailure()
|
||||
break
|
||||
}
|
||||
} // end DispatchQueue.main.async
|
||||
|
||||
} catch {
|
||||
assertionFailure(error.localizedDescription)
|
||||
return
|
||||
}
|
||||
}
|
||||
} // end Task
|
||||
})
|
||||
.store(in: &disposeBag)
|
||||
}
|
||||
}
|
||||
|
@ -180,7 +163,7 @@ extension SceneCoordinator {
|
|||
case hashtagTimeline(viewModel: HashtagTimelineViewModel)
|
||||
|
||||
// profile
|
||||
case accountList
|
||||
case accountList(viewModel: AccountListViewModel)
|
||||
case profile(viewModel: ProfileViewModel)
|
||||
case favorite(viewModel: FavoriteViewModel)
|
||||
case follower(viewModel: FollowerListViewModel)
|
||||
|
@ -260,6 +243,19 @@ extension SceneCoordinator {
|
|||
transition: .modal(animated: true, completion: nil)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
let wizardViewController = WizardViewController()
|
||||
if !wizardViewController.items.isEmpty,
|
||||
let delegate = rootViewController as? WizardViewControllerDelegate
|
||||
{
|
||||
// do not add as child view controller.
|
||||
// otherwise, the tab bar controller will add as a new tab
|
||||
wizardViewController.delegate = delegate
|
||||
wizardViewController.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
||||
wizardViewController.view.frame = rootViewController.view.bounds
|
||||
rootViewController.view.addSubview(wizardViewController.view)
|
||||
self.wizardViewController = wizardViewController
|
||||
}
|
||||
}
|
||||
|
||||
} catch {
|
||||
|
@ -431,8 +427,9 @@ private extension SceneCoordinator {
|
|||
let _viewController = HashtagTimelineViewController()
|
||||
_viewController.viewModel = viewModel
|
||||
viewController = _viewController
|
||||
case .accountList:
|
||||
case .accountList(let viewModel):
|
||||
let _viewController = AccountListViewController()
|
||||
_viewController.viewModel = viewModel
|
||||
viewController = _viewController
|
||||
case .profile(let viewModel):
|
||||
let _viewController = ProfileViewController()
|
||||
|
|
|
@ -10,4 +10,3 @@ import Foundation
|
|||
enum ComposeStatusAttachmentSection: Hashable {
|
||||
case main
|
||||
}
|
||||
|
||||
|
|
|
@ -23,13 +23,16 @@ extension DiscoverySection {
|
|||
static let logger = Logger(subsystem: "DiscoverySection", category: "logic")
|
||||
|
||||
class Configuration {
|
||||
let authContext: AuthContext
|
||||
weak var profileCardTableViewCellDelegate: ProfileCardTableViewCellDelegate?
|
||||
let familiarFollowers: Published<[Mastodon.Entity.FamiliarFollowers]>.Publisher?
|
||||
|
||||
public init(
|
||||
authContext: AuthContext,
|
||||
profileCardTableViewCellDelegate: ProfileCardTableViewCellDelegate? = nil,
|
||||
familiarFollowers: Published<[Mastodon.Entity.FamiliarFollowers]>.Publisher? = nil
|
||||
) {
|
||||
self.authContext = authContext
|
||||
self.profileCardTableViewCellDelegate = profileCardTableViewCellDelegate
|
||||
self.familiarFollowers = familiarFollowers
|
||||
}
|
||||
|
@ -73,11 +76,9 @@ extension DiscoverySection {
|
|||
} else {
|
||||
cell.profileCardView.viewModel.familiarFollowers = nil
|
||||
}
|
||||
// bind me
|
||||
cell.profileCardView.viewModel.relationshipViewModel.me = configuration.authContext.mastodonAuthenticationBox.authenticationRecord.object(in: context.managedObjectContext)?.user
|
||||
}
|
||||
context.authenticationService.activeMastodonAuthentication
|
||||
.map { $0?.user }
|
||||
.assign(to: \.me, on: cell.profileCardView.viewModel.relationshipViewModel)
|
||||
.store(in: &cell.disposeBag)
|
||||
return cell
|
||||
case .bottomLoader:
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: TimelineBottomLoaderTableViewCell.self), for: indexPath) as! TimelineBottomLoaderTableViewCell
|
||||
|
|
|
@ -24,6 +24,7 @@ enum NotificationSection: Equatable, Hashable {
|
|||
extension NotificationSection {
|
||||
|
||||
struct Configuration {
|
||||
let authContext: AuthContext
|
||||
weak var notificationTableViewCellDelegate: NotificationTableViewCellDelegate?
|
||||
let filterContext: Mastodon.Entity.Filter.Context?
|
||||
let activeFilters: Published<[Mastodon.Entity.Filter]>.Publisher?
|
||||
|
@ -74,21 +75,20 @@ extension NotificationSection {
|
|||
viewModel: NotificationTableViewCell.ViewModel,
|
||||
configuration: Configuration
|
||||
) {
|
||||
cell.notificationView.viewModel.authContext = configuration.authContext
|
||||
|
||||
StatusSection.setupStatusPollDataSource(
|
||||
context: context,
|
||||
authContext: configuration.authContext,
|
||||
statusView: cell.notificationView.statusView
|
||||
)
|
||||
|
||||
StatusSection.setupStatusPollDataSource(
|
||||
context: context,
|
||||
authContext: configuration.authContext,
|
||||
statusView: cell.notificationView.quoteStatusView
|
||||
)
|
||||
|
||||
context.authenticationService.activeMastodonAuthenticationBox
|
||||
.map { $0 as UserIdentifier? }
|
||||
.assign(to: \.userIdentifier, on: cell.notificationView.viewModel)
|
||||
.store(in: &cell.disposeBag)
|
||||
|
||||
cell.configure(
|
||||
tableView: tableView,
|
||||
viewModel: viewModel,
|
||||
|
|
|
@ -133,6 +133,7 @@ enum RecommendAccountSection: Equatable, Hashable {
|
|||
extension RecommendAccountSection {
|
||||
|
||||
struct Configuration {
|
||||
let authContext: AuthContext
|
||||
weak var suggestionAccountTableViewCellDelegate: SuggestionAccountTableViewCellDelegate?
|
||||
}
|
||||
|
||||
|
@ -150,10 +151,7 @@ extension RecommendAccountSection {
|
|||
cell.configure(user: user)
|
||||
}
|
||||
|
||||
context.authenticationService.activeMastodonAuthenticationBox
|
||||
.map { $0 as UserIdentifier? }
|
||||
.assign(to: \.userIdentifier, on: cell.viewModel)
|
||||
.store(in: &cell.disposeBag)
|
||||
cell.viewModel.userIdentifier = configuration.authContext.mastodonAuthenticationBox
|
||||
cell.delegate = configuration.suggestionAccountTableViewCellDelegate
|
||||
}
|
||||
return cell
|
||||
|
|
|
@ -23,6 +23,7 @@ enum ReportSection: Equatable, Hashable {
|
|||
extension ReportSection {
|
||||
|
||||
struct Configuration {
|
||||
let authContext: AuthContext
|
||||
}
|
||||
|
||||
static func diffableDataSource(
|
||||
|
@ -101,13 +102,11 @@ extension ReportSection {
|
|||
) {
|
||||
StatusSection.setupStatusPollDataSource(
|
||||
context: context,
|
||||
authContext: configuration.authContext,
|
||||
statusView: cell.statusView
|
||||
)
|
||||
|
||||
context.authenticationService.activeMastodonAuthenticationBox
|
||||
.map { $0 as UserIdentifier? }
|
||||
.assign(to: \.userIdentifier, on: cell.statusView.viewModel)
|
||||
.store(in: &cell.disposeBag)
|
||||
cell.statusView.viewModel.authContext = configuration.authContext
|
||||
|
||||
cell.configure(
|
||||
tableView: tableView,
|
||||
|
|
|
@ -25,6 +25,7 @@ extension SearchResultSection {
|
|||
static let logger = Logger(subsystem: "SearchResultSection", category: "logic")
|
||||
|
||||
struct Configuration {
|
||||
let authContext: AuthContext
|
||||
weak var statusViewTableViewCellDelegate: StatusTableViewCellDelegate?
|
||||
weak var userTableViewCellDelegate: UserTableViewCellDelegate?
|
||||
}
|
||||
|
@ -99,13 +100,11 @@ extension SearchResultSection {
|
|||
) {
|
||||
StatusSection.setupStatusPollDataSource(
|
||||
context: context,
|
||||
authContext: configuration.authContext,
|
||||
statusView: cell.statusView
|
||||
)
|
||||
|
||||
context.authenticationService.activeMastodonAuthenticationBox
|
||||
.map { $0 as UserIdentifier? }
|
||||
.assign(to: \.userIdentifier, on: cell.statusView.viewModel)
|
||||
.store(in: &cell.disposeBag)
|
||||
cell.statusView.viewModel.authContext = configuration.authContext
|
||||
|
||||
cell.configure(
|
||||
tableView: tableView,
|
||||
|
|
|
@ -27,6 +27,7 @@ extension StatusSection {
|
|||
static let logger = Logger(subsystem: "StatusSection", category: "logic")
|
||||
|
||||
struct Configuration {
|
||||
let authContext: AuthContext
|
||||
weak var statusTableViewCellDelegate: StatusTableViewCellDelegate?
|
||||
weak var timelineMiddleLoaderTableViewCellDelegate: TimelineMiddleLoaderTableViewCellDelegate?
|
||||
let filterContext: Mastodon.Entity.Filter.Context?
|
||||
|
@ -159,6 +160,7 @@ extension StatusSection {
|
|||
|
||||
public static func setupStatusPollDataSource(
|
||||
context: AppContext,
|
||||
authContext: AuthContext,
|
||||
statusView: StatusView
|
||||
) {
|
||||
let managedObjectContext = context.managedObjectContext
|
||||
|
@ -172,10 +174,7 @@ extension StatusSection {
|
|||
return _cell ?? PollOptionTableViewCell()
|
||||
}()
|
||||
|
||||
context.authenticationService.activeMastodonAuthenticationBox
|
||||
.map { $0 as UserIdentifier? }
|
||||
.assign(to: \.userIdentifier, on: cell.pollOptionView.viewModel)
|
||||
.store(in: &cell.disposeBag)
|
||||
cell.pollOptionView.viewModel.authContext = authContext
|
||||
|
||||
managedObjectContext.performAndWait {
|
||||
guard let option = record.object(in: managedObjectContext) else {
|
||||
|
@ -212,14 +211,13 @@ extension StatusSection {
|
|||
return true
|
||||
}()
|
||||
|
||||
if needsUpdatePoll, let authenticationBox = context.authenticationService.activeMastodonAuthenticationBox.value
|
||||
{
|
||||
if needsUpdatePoll {
|
||||
let pollRecord: ManagedObjectRecord<Poll> = .init(objectID: option.poll.objectID)
|
||||
Task { [weak context] in
|
||||
guard let context = context else { return }
|
||||
_ = try await context.apiService.poll(
|
||||
poll: pollRecord,
|
||||
authenticationBox: authenticationBox
|
||||
authenticationBox: authContext.mastodonAuthenticationBox
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -248,13 +246,11 @@ extension StatusSection {
|
|||
) {
|
||||
setupStatusPollDataSource(
|
||||
context: context,
|
||||
authContext: configuration.authContext,
|
||||
statusView: cell.statusView
|
||||
)
|
||||
|
||||
context.authenticationService.activeMastodonAuthenticationBox
|
||||
.map { $0 as UserIdentifier? }
|
||||
.assign(to: \.userIdentifier, on: cell.statusView.viewModel)
|
||||
.store(in: &cell.disposeBag)
|
||||
cell.statusView.viewModel.authContext = configuration.authContext
|
||||
|
||||
cell.configure(
|
||||
tableView: tableView,
|
||||
|
@ -277,13 +273,11 @@ extension StatusSection {
|
|||
) {
|
||||
setupStatusPollDataSource(
|
||||
context: context,
|
||||
authContext: configuration.authContext,
|
||||
statusView: cell.statusView
|
||||
)
|
||||
|
||||
context.authenticationService.activeMastodonAuthenticationBox
|
||||
.map { $0 as UserIdentifier? }
|
||||
.assign(to: \.userIdentifier, on: cell.statusView.viewModel)
|
||||
.store(in: &cell.disposeBag)
|
||||
cell.statusView.viewModel.authContext = configuration.authContext
|
||||
|
||||
cell.configure(
|
||||
tableView: tableView,
|
||||
|
|
|
@ -11,16 +11,15 @@ import MastodonCore
|
|||
|
||||
extension DataSourceFacade {
|
||||
static func responseToUserBlockAction(
|
||||
dependency: NeedsDependency,
|
||||
user: ManagedObjectRecord<MastodonUser>,
|
||||
authenticationBox: MastodonAuthenticationBox
|
||||
dependency: NeedsDependency & AuthContextProvider,
|
||||
user: ManagedObjectRecord<MastodonUser>
|
||||
) async throws {
|
||||
let selectionFeedbackGenerator = await UISelectionFeedbackGenerator()
|
||||
await selectionFeedbackGenerator.selectionChanged()
|
||||
|
||||
_ = try await dependency.context.apiService.toggleBlock(
|
||||
user: user,
|
||||
authenticationBox: authenticationBox
|
||||
authenticationBox: dependency.authContext.mastodonAuthenticationBox
|
||||
)
|
||||
} // end func
|
||||
}
|
||||
|
|
|
@ -12,16 +12,15 @@ import MastodonCore
|
|||
|
||||
extension DataSourceFacade {
|
||||
public static func responseToStatusBookmarkAction(
|
||||
provider: DataSourceProvider,
|
||||
status: ManagedObjectRecord<Status>,
|
||||
authenticationBox: MastodonAuthenticationBox
|
||||
provider: DataSourceProvider & AuthContextProvider,
|
||||
status: ManagedObjectRecord<Status>
|
||||
) async throws {
|
||||
let selectionFeedbackGenerator = await UISelectionFeedbackGenerator()
|
||||
await selectionFeedbackGenerator.selectionChanged()
|
||||
|
||||
_ = try await provider.context.apiService.bookmark(
|
||||
record: status,
|
||||
authenticationBox: authenticationBox
|
||||
authenticationBox: provider.authContext.mastodonAuthenticationBox
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,16 +12,15 @@ import MastodonCore
|
|||
|
||||
extension DataSourceFacade {
|
||||
public static func responseToStatusFavoriteAction(
|
||||
provider: DataSourceProvider,
|
||||
status: ManagedObjectRecord<Status>,
|
||||
authenticationBox: MastodonAuthenticationBox
|
||||
provider: DataSourceProvider & AuthContextProvider,
|
||||
status: ManagedObjectRecord<Status>
|
||||
) async throws {
|
||||
let selectionFeedbackGenerator = await UISelectionFeedbackGenerator()
|
||||
await selectionFeedbackGenerator.selectionChanged()
|
||||
|
||||
_ = try await provider.context.apiService.favorite(
|
||||
record: status,
|
||||
authenticationBox: authenticationBox
|
||||
authenticationBox: provider.authContext.mastodonAuthenticationBox
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,26 +14,24 @@ import MastodonLocalization
|
|||
|
||||
extension DataSourceFacade {
|
||||
static func responseToUserFollowAction(
|
||||
dependency: NeedsDependency,
|
||||
user: ManagedObjectRecord<MastodonUser>,
|
||||
authenticationBox: MastodonAuthenticationBox
|
||||
dependency: NeedsDependency & AuthContextProvider,
|
||||
user: ManagedObjectRecord<MastodonUser>
|
||||
) async throws {
|
||||
let selectionFeedbackGenerator = await UISelectionFeedbackGenerator()
|
||||
await selectionFeedbackGenerator.selectionChanged()
|
||||
|
||||
_ = try await dependency.context.apiService.toggleFollow(
|
||||
user: user,
|
||||
authenticationBox: authenticationBox
|
||||
authenticationBox: dependency.authContext.mastodonAuthenticationBox
|
||||
)
|
||||
} // end func
|
||||
}
|
||||
|
||||
extension DataSourceFacade {
|
||||
static func responseToUserFollowRequestAction(
|
||||
dependency: NeedsDependency,
|
||||
dependency: NeedsDependency & AuthContextProvider,
|
||||
notification: ManagedObjectRecord<Notification>,
|
||||
query: Mastodon.API.Account.FollowReqeustQuery,
|
||||
authenticationBox: MastodonAuthenticationBox
|
||||
query: Mastodon.API.Account.FollowReqeustQuery
|
||||
) async throws {
|
||||
let selectionFeedbackGenerator = await UISelectionFeedbackGenerator()
|
||||
await selectionFeedbackGenerator.selectionChanged()
|
||||
|
@ -72,7 +70,7 @@ extension DataSourceFacade {
|
|||
_ = try await dependency.context.apiService.followRequest(
|
||||
userID: userID,
|
||||
query: query,
|
||||
authenticationBox: authenticationBox
|
||||
authenticationBox: dependency.authContext.mastodonAuthenticationBox
|
||||
)
|
||||
} catch {
|
||||
// reset state when failure
|
||||
|
|
|
@ -7,12 +7,13 @@
|
|||
|
||||
import UIKit
|
||||
import CoreDataStack
|
||||
import MastodonCore
|
||||
import MastodonSDK
|
||||
|
||||
extension DataSourceFacade {
|
||||
@MainActor
|
||||
static func coordinateToHashtagScene(
|
||||
provider: DataSourceProvider,
|
||||
provider: DataSourceProvider & AuthContextProvider,
|
||||
tag: DataSourceItem.TagKind
|
||||
) async {
|
||||
switch tag {
|
||||
|
@ -25,11 +26,12 @@ extension DataSourceFacade {
|
|||
|
||||
@MainActor
|
||||
static func coordinateToHashtagScene(
|
||||
provider: DataSourceProvider,
|
||||
provider: DataSourceProvider & AuthContextProvider,
|
||||
tag: Mastodon.Entity.Tag
|
||||
) async {
|
||||
let hashtagTimelineViewModel = HashtagTimelineViewModel(
|
||||
context: provider.context,
|
||||
authContext: provider.authContext,
|
||||
hashtag: tag.name
|
||||
)
|
||||
|
||||
|
@ -42,7 +44,7 @@ extension DataSourceFacade {
|
|||
|
||||
@MainActor
|
||||
static func coordinateToHashtagScene(
|
||||
provider: DataSourceProvider,
|
||||
provider: DataSourceProvider & AuthContextProvider,
|
||||
tag: ManagedObjectRecord<Tag>
|
||||
) async {
|
||||
let managedObjectContext = provider.context.managedObjectContext
|
||||
|
@ -55,6 +57,7 @@ extension DataSourceFacade {
|
|||
|
||||
let hashtagTimelineViewModel = HashtagTimelineViewModel(
|
||||
context: provider.context,
|
||||
authContext: provider.authContext,
|
||||
hashtag: name
|
||||
)
|
||||
|
||||
|
|
|
@ -8,11 +8,12 @@
|
|||
import Foundation
|
||||
import CoreDataStack
|
||||
import MetaTextKit
|
||||
import MastodonCore
|
||||
|
||||
extension DataSourceFacade {
|
||||
|
||||
static func responseToMetaTextAction(
|
||||
provider: DataSourceProvider,
|
||||
provider: DataSourceProvider & AuthContextProvider,
|
||||
target: StatusTarget,
|
||||
status: ManagedObjectRecord<Status>,
|
||||
meta: Meta
|
||||
|
@ -33,7 +34,7 @@ extension DataSourceFacade {
|
|||
}
|
||||
|
||||
static func responseToMetaTextAction(
|
||||
provider: DataSourceProvider,
|
||||
provider: DataSourceProvider & AuthContextProvider,
|
||||
status: ManagedObjectRecord<Status>,
|
||||
meta: Meta
|
||||
) async {
|
||||
|
@ -47,19 +48,20 @@ extension DataSourceFacade {
|
|||
assertionFailure()
|
||||
return
|
||||
}
|
||||
if let domain = provider.context.authenticationService.activeMastodonAuthenticationBox.value?.domain, url.host == domain,
|
||||
let domain = provider.authContext.mastodonAuthenticationBox.domain
|
||||
if url.host == domain,
|
||||
url.pathComponents.count >= 4,
|
||||
url.pathComponents[0] == "/",
|
||||
url.pathComponents[1] == "web",
|
||||
url.pathComponents[2] == "statuses" {
|
||||
let statusID = url.pathComponents[3]
|
||||
let threadViewModel = RemoteThreadViewModel(context: provider.context, statusID: statusID)
|
||||
let threadViewModel = RemoteThreadViewModel(context: provider.context, authContext: provider.authContext, statusID: statusID)
|
||||
await provider.coordinator.present(scene: .thread(viewModel: threadViewModel), from: nil, transition: .show)
|
||||
} else {
|
||||
await provider.coordinator.present(scene: .safari(url: url), from: nil, transition: .safariPresent(animated: true, completion: nil))
|
||||
}
|
||||
case .hashtag(_, let hashtag, _):
|
||||
let hashtagTimelineViewModel = HashtagTimelineViewModel(context: provider.context, hashtag: hashtag)
|
||||
let hashtagTimelineViewModel = HashtagTimelineViewModel(context: provider.context, authContext: provider.authContext, hashtag: hashtag)
|
||||
await provider.coordinator.present(scene: .hashtagTimeline(viewModel: hashtagTimelineViewModel), from: provider, transition: .show)
|
||||
case .mention(_, let mention, let userInfo):
|
||||
await coordinateToProfileScene(
|
||||
|
|
|
@ -11,16 +11,15 @@ import MastodonCore
|
|||
|
||||
extension DataSourceFacade {
|
||||
static func responseToUserMuteAction(
|
||||
dependency: NeedsDependency,
|
||||
user: ManagedObjectRecord<MastodonUser>,
|
||||
authenticationBox: MastodonAuthenticationBox
|
||||
dependency: NeedsDependency & AuthContextProvider,
|
||||
user: ManagedObjectRecord<MastodonUser>
|
||||
) async throws {
|
||||
let selectionFeedbackGenerator = await UISelectionFeedbackGenerator()
|
||||
await selectionFeedbackGenerator.selectionChanged()
|
||||
|
||||
_ = try await dependency.context.apiService.toggleMute(
|
||||
user: user,
|
||||
authenticationBox: authenticationBox
|
||||
authenticationBox: dependency.authContext.mastodonAuthenticationBox
|
||||
)
|
||||
} // end func
|
||||
}
|
||||
|
|
|
@ -7,11 +7,12 @@
|
|||
|
||||
import UIKit
|
||||
import CoreDataStack
|
||||
import MastodonCore
|
||||
|
||||
extension DataSourceFacade {
|
||||
|
||||
static func coordinateToProfileScene(
|
||||
provider: DataSourceProvider,
|
||||
provider: DataSourceProvider & AuthContextProvider,
|
||||
target: StatusTarget,
|
||||
status: ManagedObjectRecord<Status>
|
||||
) async {
|
||||
|
@ -32,7 +33,7 @@ extension DataSourceFacade {
|
|||
|
||||
@MainActor
|
||||
static func coordinateToProfileScene(
|
||||
provider: DataSourceProvider,
|
||||
provider: DataSourceProvider & AuthContextProvider,
|
||||
user: ManagedObjectRecord<MastodonUser>
|
||||
) async {
|
||||
guard let user = user.object(in: provider.context.managedObjectContext) else {
|
||||
|
@ -42,6 +43,7 @@ extension DataSourceFacade {
|
|||
|
||||
let profileViewModel = CachedProfileViewModel(
|
||||
context: provider.context,
|
||||
authContext: provider.authContext,
|
||||
mastodonUser: user
|
||||
)
|
||||
|
||||
|
@ -57,13 +59,12 @@ extension DataSourceFacade {
|
|||
extension DataSourceFacade {
|
||||
|
||||
static func coordinateToProfileScene(
|
||||
provider: DataSourceProvider,
|
||||
provider: DataSourceProvider & AuthContextProvider,
|
||||
status: ManagedObjectRecord<Status>,
|
||||
mention: String, // username,
|
||||
userInfo: [AnyHashable: Any]?
|
||||
) async {
|
||||
guard let authenticationBox = provider.context.authenticationService.activeMastodonAuthenticationBox.value else { return }
|
||||
let domain = authenticationBox.domain
|
||||
let domain = provider.authContext.mastodonAuthenticationBox.domain
|
||||
|
||||
let href = userInfo?["href"] as? String
|
||||
guard let url = href.flatMap({ URL(string: $0) }) else { return }
|
||||
|
@ -85,8 +86,8 @@ extension DataSourceFacade {
|
|||
let userID = mention.id
|
||||
let profileViewModel: ProfileViewModel = {
|
||||
// check if self
|
||||
guard userID != authenticationBox.userID else {
|
||||
return MeProfileViewModel(context: provider.context)
|
||||
guard userID != provider.authContext.mastodonAuthenticationBox.userID else {
|
||||
return MeProfileViewModel(context: provider.context, authContext: provider.authContext)
|
||||
}
|
||||
|
||||
let request = MastodonUser.sortedFetchRequest
|
||||
|
@ -95,9 +96,9 @@ extension DataSourceFacade {
|
|||
let _user = provider.context.managedObjectContext.safeFetch(request).first
|
||||
|
||||
if let user = _user {
|
||||
return CachedProfileViewModel(context: provider.context, mastodonUser: user)
|
||||
return CachedProfileViewModel(context: provider.context, authContext: provider.authContext, mastodonUser: user)
|
||||
} else {
|
||||
return RemoteProfileViewModel(context: provider.context, userID: userID)
|
||||
return RemoteProfileViewModel(context: provider.context, authContext: provider.authContext, userID: userID)
|
||||
}
|
||||
}()
|
||||
|
||||
|
|
|
@ -12,16 +12,15 @@ import MastodonUI
|
|||
|
||||
extension DataSourceFacade {
|
||||
static func responseToStatusReblogAction(
|
||||
provider: DataSourceProvider,
|
||||
status: ManagedObjectRecord<Status>,
|
||||
authenticationBox: MastodonAuthenticationBox
|
||||
provider: DataSourceProvider & AuthContextProvider,
|
||||
status: ManagedObjectRecord<Status>
|
||||
) async throws {
|
||||
let selectionFeedbackGenerator = await UISelectionFeedbackGenerator()
|
||||
await selectionFeedbackGenerator.selectionChanged()
|
||||
|
||||
_ = try await provider.context.apiService.reblog(
|
||||
record: status,
|
||||
authenticationBox: authenticationBox
|
||||
authenticationBox: provider.authContext.mastodonAuthenticationBox
|
||||
)
|
||||
} // end func
|
||||
}
|
||||
|
|
|
@ -12,18 +12,18 @@ import MastodonCore
|
|||
extension DataSourceFacade {
|
||||
|
||||
static func responseToCreateSearchHistory(
|
||||
provider: DataSourceProvider,
|
||||
provider: DataSourceProvider & AuthContextProvider,
|
||||
item: DataSourceItem
|
||||
) async {
|
||||
switch item {
|
||||
case .status:
|
||||
break // not create search history for status
|
||||
case .user(let record):
|
||||
let authenticationBox = provider.context.authenticationService.activeMastodonAuthenticationBox.value
|
||||
let authenticationBox = provider.authContext.mastodonAuthenticationBox
|
||||
let managedObjectContext = provider.context.backgroundManagedObjectContext
|
||||
|
||||
try? await managedObjectContext.performChanges {
|
||||
guard let me = authenticationBox?.authenticationRecord.object(in: managedObjectContext)?.user else { return }
|
||||
guard let me = authenticationBox.authenticationRecord.object(in: managedObjectContext)?.user else { return }
|
||||
guard let user = record.object(in: managedObjectContext) else { return }
|
||||
_ = Persistence.SearchHistory.createOrMerge(
|
||||
in: managedObjectContext,
|
||||
|
@ -35,13 +35,12 @@ extension DataSourceFacade {
|
|||
)
|
||||
} // end try? await managedObjectContext.performChanges { … }
|
||||
case .hashtag(let tag):
|
||||
let _authenticationBox = provider.context.authenticationService.activeMastodonAuthenticationBox.value
|
||||
let authenticationBox = provider.authContext.mastodonAuthenticationBox
|
||||
let managedObjectContext = provider.context.backgroundManagedObjectContext
|
||||
|
||||
switch tag {
|
||||
case .entity(let entity):
|
||||
try? await managedObjectContext.performChanges {
|
||||
guard let authenticationBox = _authenticationBox else { return }
|
||||
guard let me = authenticationBox.authenticationRecord.object(in: managedObjectContext)?.user else { return }
|
||||
|
||||
let now = Date()
|
||||
|
@ -67,7 +66,7 @@ extension DataSourceFacade {
|
|||
} // end try? await managedObjectContext.performChanges { … }
|
||||
case .record(let record):
|
||||
try? await managedObjectContext.performChanges {
|
||||
guard let authenticationBox = _authenticationBox else { return }
|
||||
let authenticationBox = provider.authContext.mastodonAuthenticationBox
|
||||
guard let me = authenticationBox.authenticationRecord.object(in: managedObjectContext)?.user else { return }
|
||||
guard let tag = record.object(in: managedObjectContext) else { return }
|
||||
|
||||
|
@ -93,13 +92,12 @@ extension DataSourceFacade {
|
|||
extension DataSourceFacade {
|
||||
|
||||
static func responseToDeleteSearchHistory(
|
||||
provider: DataSourceProvider
|
||||
provider: DataSourceProvider & AuthContextProvider
|
||||
) async throws {
|
||||
let _authenticationBox = provider.context.authenticationService.activeMastodonAuthenticationBox.value
|
||||
let authenticationBox = provider.authContext.mastodonAuthenticationBox
|
||||
let managedObjectContext = provider.context.backgroundManagedObjectContext
|
||||
|
||||
try await managedObjectContext.performChanges {
|
||||
guard let authenticationBox = _authenticationBox else { return }
|
||||
guard let _ = authenticationBox.authenticationRecord.object(in: managedObjectContext)?.user else { return }
|
||||
let request = SearchHistory.sortedFetchRequest
|
||||
request.predicate = SearchHistory.predicate(
|
||||
|
|
|
@ -15,13 +15,12 @@ import MastodonLocalization
|
|||
extension DataSourceFacade {
|
||||
|
||||
static func responseToDeleteStatus(
|
||||
dependency: NeedsDependency,
|
||||
status: ManagedObjectRecord<Status>,
|
||||
authenticationBox: MastodonAuthenticationBox
|
||||
dependency: NeedsDependency & AuthContextProvider,
|
||||
status: ManagedObjectRecord<Status>
|
||||
) async throws {
|
||||
_ = try await dependency.context.apiService.deleteStatus(
|
||||
status: status,
|
||||
authenticationBox: authenticationBox
|
||||
authenticationBox: dependency.authContext.mastodonAuthenticationBox
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -81,10 +80,9 @@ extension DataSourceFacade {
|
|||
extension DataSourceFacade {
|
||||
@MainActor
|
||||
static func responseToActionToolbar(
|
||||
provider: DataSourceProvider,
|
||||
provider: DataSourceProvider & AuthContextProvider,
|
||||
status: ManagedObjectRecord<Status>,
|
||||
action: ActionToolbarContainer.Action,
|
||||
authenticationBox: MastodonAuthenticationBox,
|
||||
sender: UIButton
|
||||
) async throws {
|
||||
let managedObjectContext = provider.context.managedObjectContext
|
||||
|
@ -100,16 +98,15 @@ extension DataSourceFacade {
|
|||
|
||||
switch action {
|
||||
case .reply:
|
||||
guard let authenticationBox = provider.context.authenticationService.activeMastodonAuthenticationBox.value else { return }
|
||||
let selectionFeedbackGenerator = UISelectionFeedbackGenerator()
|
||||
selectionFeedbackGenerator.selectionChanged()
|
||||
|
||||
let composeViewModel = ComposeViewModel(
|
||||
context: provider.context,
|
||||
composeKind: .reply(status: status),
|
||||
authenticationBox: authenticationBox
|
||||
authContext: provider.authContext
|
||||
)
|
||||
provider.coordinator.present(
|
||||
_ = provider.coordinator.present(
|
||||
scene: .compose(viewModel: composeViewModel),
|
||||
from: provider,
|
||||
transition: .modal(animated: true, completion: nil)
|
||||
|
@ -117,20 +114,17 @@ extension DataSourceFacade {
|
|||
case .reblog:
|
||||
try await DataSourceFacade.responseToStatusReblogAction(
|
||||
provider: provider,
|
||||
status: status,
|
||||
authenticationBox: authenticationBox
|
||||
status: status
|
||||
)
|
||||
case .like:
|
||||
try await DataSourceFacade.responseToStatusFavoriteAction(
|
||||
provider: provider,
|
||||
status: status,
|
||||
authenticationBox: authenticationBox
|
||||
status: status
|
||||
)
|
||||
case .bookmark:
|
||||
try await DataSourceFacade.responseToStatusBookmarkAction(
|
||||
provider: provider,
|
||||
status: status,
|
||||
authenticationBox: authenticationBox
|
||||
status: status
|
||||
)
|
||||
case .share:
|
||||
try await DataSourceFacade.responseToStatusShareAction(
|
||||
|
@ -155,10 +149,9 @@ extension DataSourceFacade {
|
|||
|
||||
@MainActor
|
||||
static func responseToMenuAction(
|
||||
dependency: NeedsDependency & UIViewController,
|
||||
dependency: UIViewController & NeedsDependency & AuthContextProvider,
|
||||
action: MastodonMenu.Action,
|
||||
menuContext: MenuContext,
|
||||
authenticationBox: MastodonAuthenticationBox
|
||||
menuContext: MenuContext
|
||||
) async throws {
|
||||
switch action {
|
||||
case .muteUser(let actionContext):
|
||||
|
@ -181,8 +174,7 @@ extension DataSourceFacade {
|
|||
guard let user = _user else { return }
|
||||
try await DataSourceFacade.responseToUserMuteAction(
|
||||
dependency: dependency,
|
||||
user: user,
|
||||
authenticationBox: authenticationBox
|
||||
user: user
|
||||
)
|
||||
} // end Task
|
||||
}
|
||||
|
@ -210,8 +202,7 @@ extension DataSourceFacade {
|
|||
guard let user = _user else { return }
|
||||
try await DataSourceFacade.responseToUserBlockAction(
|
||||
dependency: dependency,
|
||||
user: user,
|
||||
authenticationBox: authenticationBox
|
||||
user: user
|
||||
)
|
||||
} // end Task
|
||||
}
|
||||
|
@ -225,11 +216,12 @@ extension DataSourceFacade {
|
|||
|
||||
let reportViewModel = ReportViewModel(
|
||||
context: dependency.context,
|
||||
authContext: dependency.authContext,
|
||||
user: user,
|
||||
status: menuContext.status
|
||||
)
|
||||
|
||||
dependency.coordinator.present(
|
||||
_ = dependency.coordinator.present(
|
||||
scene: .report(viewModel: reportViewModel),
|
||||
from: dependency,
|
||||
transition: .modal(animated: true, completion: nil)
|
||||
|
@ -246,7 +238,7 @@ extension DataSourceFacade {
|
|||
user: user
|
||||
)
|
||||
guard let activityViewController = _activityViewController else { return }
|
||||
dependency.coordinator.present(
|
||||
_ = dependency.coordinator.present(
|
||||
scene: .activityViewController(
|
||||
activityViewController: activityViewController,
|
||||
sourceView: menuContext.button,
|
||||
|
@ -270,8 +262,7 @@ extension DataSourceFacade {
|
|||
Task {
|
||||
try await DataSourceFacade.responseToDeleteStatus(
|
||||
dependency: dependency,
|
||||
status: status,
|
||||
authenticationBox: authenticationBox
|
||||
status: status
|
||||
)
|
||||
} // end Task
|
||||
}
|
||||
|
|
|
@ -8,10 +8,11 @@
|
|||
import Foundation
|
||||
import CoreData
|
||||
import CoreDataStack
|
||||
import MastodonCore
|
||||
|
||||
extension DataSourceFacade {
|
||||
static func coordinateToStatusThreadScene(
|
||||
provider: DataSourceProvider,
|
||||
provider: DataSourceProvider & AuthContextProvider,
|
||||
target: StatusTarget,
|
||||
status: ManagedObjectRecord<Status>
|
||||
) async {
|
||||
|
@ -39,14 +40,15 @@ extension DataSourceFacade {
|
|||
|
||||
@MainActor
|
||||
static func coordinateToStatusThreadScene(
|
||||
provider: DataSourceProvider,
|
||||
provider: DataSourceProvider & AuthContextProvider,
|
||||
root: StatusItem.Thread
|
||||
) async {
|
||||
let threadViewModel = ThreadViewModel(
|
||||
context: provider.context,
|
||||
authContext: provider.authContext,
|
||||
optionalRoot: root
|
||||
)
|
||||
provider.coordinator.present(
|
||||
_ = provider.coordinator.present(
|
||||
scene: .thread(viewModel: threadViewModel),
|
||||
from: provider,
|
||||
transition: .show
|
||||
|
|
|
@ -7,18 +7,18 @@
|
|||
|
||||
import UIKit
|
||||
import MetaTextKit
|
||||
import MastodonUI
|
||||
import CoreDataStack
|
||||
import MastodonCore
|
||||
import MastodonUI
|
||||
|
||||
// MARK: - Notification AuthorMenuAction
|
||||
extension NotificationTableViewCellDelegate where Self: DataSourceProvider {
|
||||
extension NotificationTableViewCellDelegate where Self: DataSourceProvider & AuthContextProvider {
|
||||
func tableViewCell(
|
||||
_ cell: UITableViewCell,
|
||||
notificationView: NotificationView,
|
||||
menuButton button: UIButton,
|
||||
didSelectAction action: MastodonMenu.Action
|
||||
) {
|
||||
guard let authenticationBox = context.authenticationService.activeMastodonAuthenticationBox.value else { return }
|
||||
Task {
|
||||
let source = DataSourceItem.Source(tableViewCell: cell, indexPath: nil)
|
||||
guard let item = await item(from: source) else {
|
||||
|
@ -47,15 +47,14 @@ extension NotificationTableViewCellDelegate where Self: DataSourceProvider {
|
|||
status: nil,
|
||||
button: button,
|
||||
barButtonItem: nil
|
||||
),
|
||||
authenticationBox: authenticationBox
|
||||
)
|
||||
)
|
||||
} // end Task
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Notification Author Avatar
|
||||
extension NotificationTableViewCellDelegate where Self: DataSourceProvider {
|
||||
extension NotificationTableViewCellDelegate where Self: DataSourceProvider & AuthContextProvider {
|
||||
func tableViewCell(
|
||||
_ cell: UITableViewCell,
|
||||
notificationView: NotificationView,
|
||||
|
@ -88,7 +87,7 @@ extension NotificationTableViewCellDelegate where Self: DataSourceProvider {
|
|||
}
|
||||
|
||||
// MARK: - Follow Request
|
||||
extension NotificationTableViewCellDelegate where Self: DataSourceProvider {
|
||||
extension NotificationTableViewCellDelegate where Self: DataSourceProvider & AuthContextProvider {
|
||||
|
||||
func tableViewCell(
|
||||
_ cell: UITableViewCell,
|
||||
|
@ -106,15 +105,10 @@ extension NotificationTableViewCellDelegate where Self: DataSourceProvider {
|
|||
return
|
||||
}
|
||||
|
||||
guard let authenticationBox = context.authenticationService.activeMastodonAuthenticationBox.value else {
|
||||
return
|
||||
}
|
||||
|
||||
try await DataSourceFacade.responseToUserFollowRequestAction(
|
||||
dependency: self,
|
||||
notification: notification,
|
||||
query: .accept,
|
||||
authenticationBox: authenticationBox
|
||||
query: .accept
|
||||
)
|
||||
} // end Task
|
||||
}
|
||||
|
@ -135,15 +129,10 @@ extension NotificationTableViewCellDelegate where Self: DataSourceProvider {
|
|||
return
|
||||
}
|
||||
|
||||
guard let authenticationBox = context.authenticationService.activeMastodonAuthenticationBox.value else {
|
||||
return
|
||||
}
|
||||
|
||||
try await DataSourceFacade.responseToUserFollowRequestAction(
|
||||
dependency: self,
|
||||
notification: notification,
|
||||
query: .reject,
|
||||
authenticationBox: authenticationBox
|
||||
query: .reject
|
||||
)
|
||||
} // end Task
|
||||
}
|
||||
|
@ -151,7 +140,7 @@ extension NotificationTableViewCellDelegate where Self: DataSourceProvider {
|
|||
}
|
||||
|
||||
// MARK: - Status Content
|
||||
extension NotificationTableViewCellDelegate where Self: DataSourceProvider {
|
||||
extension NotificationTableViewCellDelegate where Self: DataSourceProvider & AuthContextProvider {
|
||||
func tableViewCell(
|
||||
_ cell: UITableViewCell,
|
||||
notificationView: NotificationView,
|
||||
|
@ -279,7 +268,7 @@ extension NotificationTableViewCellDelegate where Self: DataSourceProvider & Med
|
|||
}
|
||||
|
||||
// MARK: - Status Toolbar
|
||||
extension NotificationTableViewCellDelegate where Self: DataSourceProvider {
|
||||
extension NotificationTableViewCellDelegate where Self: DataSourceProvider & AuthContextProvider {
|
||||
func tableViewCell(
|
||||
_ cell: UITableViewCell,
|
||||
notificationView: NotificationView,
|
||||
|
@ -287,7 +276,6 @@ extension NotificationTableViewCellDelegate where Self: DataSourceProvider {
|
|||
buttonDidPressed button: UIButton,
|
||||
action: ActionToolbarContainer.Action
|
||||
) {
|
||||
guard let authenticationBox = context.authenticationService.activeMastodonAuthenticationBox.value else { return }
|
||||
Task {
|
||||
let source = DataSourceItem.Source(tableViewCell: cell, indexPath: nil)
|
||||
guard let item = await item(from: source) else {
|
||||
|
@ -311,7 +299,6 @@ extension NotificationTableViewCellDelegate where Self: DataSourceProvider {
|
|||
provider: self,
|
||||
status: status,
|
||||
action: action,
|
||||
authenticationBox: authenticationBox,
|
||||
sender: button
|
||||
)
|
||||
} // end Task
|
||||
|
@ -319,7 +306,7 @@ extension NotificationTableViewCellDelegate where Self: DataSourceProvider {
|
|||
}
|
||||
|
||||
// MARK: - Status Author Avatar
|
||||
extension NotificationTableViewCellDelegate where Self: DataSourceProvider {
|
||||
extension NotificationTableViewCellDelegate where Self: DataSourceProvider & AuthContextProvider {
|
||||
func tableViewCell(
|
||||
_ cell: UITableViewCell,
|
||||
notificationView: NotificationView,
|
||||
|
@ -354,7 +341,7 @@ extension NotificationTableViewCellDelegate where Self: DataSourceProvider {
|
|||
}
|
||||
|
||||
// MARK: - Status Content
|
||||
extension NotificationTableViewCellDelegate where Self: DataSourceProvider {
|
||||
extension NotificationTableViewCellDelegate where Self: DataSourceProvider & AuthContextProvider {
|
||||
|
||||
func tableViewCell(
|
||||
_ cell: UITableViewCell,
|
||||
|
@ -530,7 +517,7 @@ extension NotificationTableViewCellDelegate where Self: DataSourceProvider {
|
|||
}
|
||||
|
||||
// MARK: a11y
|
||||
extension NotificationTableViewCellDelegate where Self: DataSourceProvider {
|
||||
extension NotificationTableViewCellDelegate where Self: DataSourceProvider & AuthContextProvider {
|
||||
func tableViewCell(_ cell: UITableViewCell, notificationView: NotificationView, accessibilityActivate: Void) {
|
||||
Task {
|
||||
let source = DataSourceItem.Source(tableViewCell: cell, indexPath: nil)
|
||||
|
|
|
@ -8,10 +8,11 @@
|
|||
import UIKit
|
||||
import CoreDataStack
|
||||
import MetaTextKit
|
||||
import MastodonCore
|
||||
import MastodonUI
|
||||
|
||||
// MARK: - header
|
||||
extension StatusTableViewCellDelegate where Self: DataSourceProvider {
|
||||
extension StatusTableViewCellDelegate where Self: DataSourceProvider & AuthContextProvider {
|
||||
|
||||
func tableViewCell(
|
||||
_ cell: UITableViewCell,
|
||||
|
@ -64,7 +65,7 @@ extension StatusTableViewCellDelegate where Self: DataSourceProvider {
|
|||
}
|
||||
|
||||
// MARK: - avatar button
|
||||
extension StatusTableViewCellDelegate where Self: DataSourceProvider {
|
||||
extension StatusTableViewCellDelegate where Self: DataSourceProvider & AuthContextProvider {
|
||||
|
||||
func tableViewCell(
|
||||
_ cell: UITableViewCell,
|
||||
|
@ -92,7 +93,7 @@ extension StatusTableViewCellDelegate where Self: DataSourceProvider {
|
|||
}
|
||||
|
||||
// MARK: - content
|
||||
extension StatusTableViewCellDelegate where Self: DataSourceProvider {
|
||||
extension StatusTableViewCellDelegate where Self: DataSourceProvider & AuthContextProvider {
|
||||
|
||||
func tableViewCell(
|
||||
_ cell: UITableViewCell,
|
||||
|
@ -169,7 +170,7 @@ extension StatusTableViewCellDelegate where Self: DataSourceProvider & MediaPrev
|
|||
|
||||
|
||||
// MARK: - poll
|
||||
extension StatusTableViewCellDelegate where Self: DataSourceProvider {
|
||||
extension StatusTableViewCellDelegate where Self: DataSourceProvider & AuthContextProvider {
|
||||
|
||||
func tableViewCell(
|
||||
_ cell: UITableViewCell,
|
||||
|
@ -177,7 +178,6 @@ extension StatusTableViewCellDelegate where Self: DataSourceProvider {
|
|||
pollTableView tableView: UITableView,
|
||||
didSelectRowAt indexPath: IndexPath
|
||||
) {
|
||||
guard let authenticationBox = context.authenticationService.activeMastodonAuthenticationBox.value else { return }
|
||||
guard let pollTableViewDiffableDataSource = statusView.pollTableViewDiffableDataSource else { return }
|
||||
guard let pollItem = pollTableViewDiffableDataSource.itemIdentifier(for: indexPath) else { return }
|
||||
|
||||
|
@ -226,7 +226,7 @@ extension StatusTableViewCellDelegate where Self: DataSourceProvider {
|
|||
_ = try await context.apiService.vote(
|
||||
poll: poll,
|
||||
choices: [choice],
|
||||
authenticationBox: authenticationBox
|
||||
authenticationBox: authContext.mastodonAuthenticationBox
|
||||
)
|
||||
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): vote poll for \(choice) success")
|
||||
} catch {
|
||||
|
@ -248,7 +248,6 @@ extension StatusTableViewCellDelegate where Self: DataSourceProvider {
|
|||
statusView: StatusView,
|
||||
pollVoteButtonPressed button: UIButton
|
||||
) {
|
||||
guard let authenticationBox = context.authenticationService.activeMastodonAuthenticationBox.value else { return }
|
||||
guard let pollTableViewDiffableDataSource = statusView.pollTableViewDiffableDataSource else { return }
|
||||
guard let firstPollItem = pollTableViewDiffableDataSource.snapshot().itemIdentifiers.first else { return }
|
||||
guard case let .option(firstPollOption) = firstPollItem else { return }
|
||||
|
@ -284,7 +283,7 @@ extension StatusTableViewCellDelegate where Self: DataSourceProvider {
|
|||
_ = try await context.apiService.vote(
|
||||
poll: poll,
|
||||
choices: choices,
|
||||
authenticationBox: authenticationBox
|
||||
authenticationBox: authContext.mastodonAuthenticationBox
|
||||
)
|
||||
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): vote poll for \(choices) success")
|
||||
} catch {
|
||||
|
@ -303,7 +302,7 @@ extension StatusTableViewCellDelegate where Self: DataSourceProvider {
|
|||
}
|
||||
|
||||
// MARK: - toolbar
|
||||
extension StatusTableViewCellDelegate where Self: DataSourceProvider {
|
||||
extension StatusTableViewCellDelegate where Self: DataSourceProvider & AuthContextProvider {
|
||||
func tableViewCell(
|
||||
_ cell: UITableViewCell,
|
||||
statusView: StatusView,
|
||||
|
@ -311,7 +310,6 @@ extension StatusTableViewCellDelegate where Self: DataSourceProvider {
|
|||
buttonDidPressed button: UIButton,
|
||||
action: ActionToolbarContainer.Action
|
||||
) {
|
||||
guard let authenticationBox = context.authenticationService.activeMastodonAuthenticationBox.value else { return }
|
||||
Task {
|
||||
let source = DataSourceItem.Source(tableViewCell: cell, indexPath: nil)
|
||||
guard let item = await item(from: source) else {
|
||||
|
@ -327,7 +325,6 @@ extension StatusTableViewCellDelegate where Self: DataSourceProvider {
|
|||
provider: self,
|
||||
status: status,
|
||||
action: action,
|
||||
authenticationBox: authenticationBox,
|
||||
sender: button
|
||||
)
|
||||
} // end Task
|
||||
|
@ -336,14 +333,13 @@ extension StatusTableViewCellDelegate where Self: DataSourceProvider {
|
|||
}
|
||||
|
||||
// MARK: - menu button
|
||||
extension StatusTableViewCellDelegate where Self: DataSourceProvider {
|
||||
extension StatusTableViewCellDelegate where Self: DataSourceProvider & AuthContextProvider {
|
||||
func tableViewCell(
|
||||
_ cell: UITableViewCell,
|
||||
statusView: StatusView,
|
||||
menuButton button: UIButton,
|
||||
didSelectAction action: MastodonMenu.Action
|
||||
) {
|
||||
guard let authenticationBox = context.authenticationService.activeMastodonAuthenticationBox.value else { return }
|
||||
Task {
|
||||
let source = DataSourceItem.Source(tableViewCell: cell, indexPath: nil)
|
||||
guard let item = await item(from: source) else {
|
||||
|
@ -372,8 +368,7 @@ extension StatusTableViewCellDelegate where Self: DataSourceProvider {
|
|||
status: status,
|
||||
button: button,
|
||||
barButtonItem: nil
|
||||
),
|
||||
authenticationBox: authenticationBox
|
||||
)
|
||||
)
|
||||
} // end Task
|
||||
}
|
||||
|
@ -475,7 +470,7 @@ extension StatusTableViewCellDelegate where Self: DataSourceProvider {
|
|||
}
|
||||
|
||||
// MARK: - StatusMetricView
|
||||
extension StatusTableViewCellDelegate where Self: DataSourceProvider {
|
||||
extension StatusTableViewCellDelegate where Self: DataSourceProvider & AuthContextProvider {
|
||||
func tableViewCell(_ cell: UITableViewCell, statusView: StatusView, statusMetricView: StatusMetricView, reblogButtonDidPressed button: UIButton) {
|
||||
Task {
|
||||
let source = DataSourceItem.Source(tableViewCell: cell, indexPath: nil)
|
||||
|
@ -489,6 +484,7 @@ extension StatusTableViewCellDelegate where Self: DataSourceProvider {
|
|||
}
|
||||
let userListViewModel = UserListViewModel(
|
||||
context: context,
|
||||
authContext: authContext,
|
||||
kind: .rebloggedBy(status: status)
|
||||
)
|
||||
await coordinator.present(
|
||||
|
@ -512,6 +508,7 @@ extension StatusTableViewCellDelegate where Self: DataSourceProvider {
|
|||
}
|
||||
let userListViewModel = UserListViewModel(
|
||||
context: context,
|
||||
authContext: authContext,
|
||||
kind: .favoritedBy(status: status)
|
||||
)
|
||||
await coordinator.present(
|
||||
|
@ -524,7 +521,7 @@ extension StatusTableViewCellDelegate where Self: DataSourceProvider {
|
|||
}
|
||||
|
||||
// MARK: a11y
|
||||
extension StatusTableViewCellDelegate where Self: DataSourceProvider {
|
||||
extension StatusTableViewCellDelegate where Self: DataSourceProvider & AuthContextProvider {
|
||||
func tableViewCell(_ cell: UITableViewCell, statusView: StatusView, accessibilityActivate: Void) {
|
||||
Task {
|
||||
let source = DataSourceItem.Source(tableViewCell: cell, indexPath: nil)
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
import os.log
|
||||
import UIKit
|
||||
import CoreDataStack
|
||||
import MastodonCore
|
||||
|
||||
extension StatusTableViewControllerNavigateableCore where Self: DataSourceProvider & StatusTableViewControllerNavigateableRelay {
|
||||
|
||||
|
@ -30,7 +31,7 @@ extension StatusTableViewControllerNavigateableCore where Self: DataSourceProvid
|
|||
|
||||
}
|
||||
|
||||
extension StatusTableViewControllerNavigateableCore where Self: DataSourceProvider {
|
||||
extension StatusTableViewControllerNavigateableCore where Self: DataSourceProvider & AuthContextProvider {
|
||||
|
||||
func statusKeyCommandHandler(_ sender: UIKeyCommand) {
|
||||
guard let rawValue = sender.propertyList as? String,
|
||||
|
@ -53,7 +54,7 @@ extension StatusTableViewControllerNavigateableCore where Self: DataSourceProvid
|
|||
}
|
||||
|
||||
// status coordinate
|
||||
extension StatusTableViewControllerNavigateableCore where Self: DataSourceProvider {
|
||||
extension StatusTableViewControllerNavigateableCore where Self: DataSourceProvider & AuthContextProvider {
|
||||
|
||||
@MainActor
|
||||
private func statusRecord() async -> ManagedObjectRecord<Status>? {
|
||||
|
@ -93,14 +94,13 @@ extension StatusTableViewControllerNavigateableCore where Self: DataSourceProvid
|
|||
private func replyStatus() async {
|
||||
guard let status = await statusRecord() else { return }
|
||||
|
||||
guard let authenticationBox = self.context.authenticationService.activeMastodonAuthenticationBox.value else { return }
|
||||
let selectionFeedbackGenerator = UISelectionFeedbackGenerator()
|
||||
selectionFeedbackGenerator.selectionChanged()
|
||||
|
||||
let composeViewModel = ComposeViewModel(
|
||||
context: self.context,
|
||||
composeKind: .reply(status: status),
|
||||
authenticationBox: authenticationBox
|
||||
authContext: authContext
|
||||
)
|
||||
self.coordinator.present(
|
||||
scene: .compose(viewModel: composeViewModel),
|
||||
|
@ -144,19 +144,16 @@ extension StatusTableViewControllerNavigateableCore where Self: DataSourceProvid
|
|||
}
|
||||
|
||||
// toggle
|
||||
extension StatusTableViewControllerNavigateableCore where Self: DataSourceProvider {
|
||||
extension StatusTableViewControllerNavigateableCore where Self: DataSourceProvider & AuthContextProvider {
|
||||
|
||||
@MainActor
|
||||
private func toggleReblog() async {
|
||||
guard let status = await statusRecord() else { return }
|
||||
|
||||
guard let authenticationBox = self.context.authenticationService.activeMastodonAuthenticationBox.value else { return }
|
||||
|
||||
do {
|
||||
try await DataSourceFacade.responseToStatusReblogAction(
|
||||
provider: self,
|
||||
status: status,
|
||||
authenticationBox: authenticationBox
|
||||
status: status
|
||||
)
|
||||
} catch {
|
||||
assertionFailure()
|
||||
|
@ -167,13 +164,10 @@ extension StatusTableViewControllerNavigateableCore where Self: DataSourceProvid
|
|||
private func toggleFavorite() async {
|
||||
guard let status = await statusRecord() else { return }
|
||||
|
||||
guard let authenticationBox = self.context.authenticationService.activeMastodonAuthenticationBox.value else { return }
|
||||
|
||||
do {
|
||||
try await DataSourceFacade.responseToStatusFavoriteAction(
|
||||
provider: self,
|
||||
status: status,
|
||||
authenticationBox: authenticationBox
|
||||
status: status
|
||||
)
|
||||
} catch {
|
||||
assertionFailure()
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import os.log
|
||||
import UIKit
|
||||
import MastodonCore
|
||||
|
||||
extension TableViewControllerNavigateableCore where Self: TableViewControllerNavigateableRelay {
|
||||
var navigationKeyCommands: [UIKeyCommand] {
|
||||
|
@ -124,7 +125,7 @@ extension TableViewControllerNavigateableCore {
|
|||
|
||||
}
|
||||
|
||||
extension TableViewControllerNavigateableCore where Self: DataSourceProvider {
|
||||
extension TableViewControllerNavigateableCore where Self: DataSourceProvider & AuthContextProvider {
|
||||
func open() {
|
||||
guard let indexPathForSelectedRow = tableView.indexPathForSelectedRow else { return }
|
||||
let source = DataSourceItem.Source(indexPath: indexPathForSelectedRow)
|
||||
|
|
|
@ -12,7 +12,7 @@ import MastodonCore
|
|||
import MastodonUI
|
||||
import MastodonLocalization
|
||||
|
||||
extension UITableViewDelegate where Self: DataSourceProvider {
|
||||
extension UITableViewDelegate where Self: DataSourceProvider & AuthContextProvider {
|
||||
|
||||
func aspectTableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): indexPath: \(indexPath.debugDescription)")
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
// Created by Cirno MainasuK on 2021-9-13.
|
||||
//
|
||||
|
||||
import os.log
|
||||
import UIKit
|
||||
import Combine
|
||||
import CoreData
|
||||
|
@ -14,43 +15,43 @@ import MastodonMeta
|
|||
import MastodonCore
|
||||
import MastodonUI
|
||||
|
||||
final class AccountListViewModel {
|
||||
final class AccountListViewModel: NSObject {
|
||||
|
||||
var disposeBag = Set<AnyCancellable>()
|
||||
|
||||
// input
|
||||
let context: AppContext
|
||||
let authContext: AuthContext
|
||||
let mastodonAuthenticationFetchedResultsController: NSFetchedResultsController<MastodonAuthentication>
|
||||
|
||||
// output
|
||||
let authentications = CurrentValueSubject<[Item], Never>([])
|
||||
let activeMastodonUserObjectID = CurrentValueSubject<NSManagedObjectID?, Never>(nil)
|
||||
@Published var authentications: [ManagedObjectRecord<MastodonAuthentication>] = []
|
||||
@Published var items: [Item] = []
|
||||
|
||||
let dataSourceDidUpdate = PassthroughSubject<Void, Never>()
|
||||
var diffableDataSource: UITableViewDiffableDataSource<Section, Item>!
|
||||
|
||||
init(context: AppContext) {
|
||||
init(context: AppContext, authContext: AuthContext) {
|
||||
self.context = context
|
||||
|
||||
Publishers.CombineLatest(
|
||||
context.authenticationService.mastodonAuthentications,
|
||||
context.authenticationService.activeMastodonAuthentication
|
||||
self.authContext = authContext
|
||||
self.mastodonAuthenticationFetchedResultsController = {
|
||||
let fetchRequest = MastodonAuthentication.sortedFetchRequest
|
||||
fetchRequest.returnsObjectsAsFaults = false
|
||||
fetchRequest.fetchBatchSize = 20
|
||||
let controller = NSFetchedResultsController(
|
||||
fetchRequest: fetchRequest,
|
||||
managedObjectContext: context.managedObjectContext,
|
||||
sectionNameKeyPath: nil,
|
||||
cacheName: nil
|
||||
)
|
||||
.sink { [weak self] authentications, activeAuthentication in
|
||||
guard let self = self else { return }
|
||||
var items: [Item] = []
|
||||
var activeMastodonUserObjectID: NSManagedObjectID?
|
||||
for authentication in authentications {
|
||||
let item = Item.authentication(objectID: authentication.objectID)
|
||||
items.append(item)
|
||||
if authentication === activeAuthentication {
|
||||
activeMastodonUserObjectID = authentication.user.objectID
|
||||
}
|
||||
}
|
||||
self.authentications.value = items
|
||||
self.activeMastodonUserObjectID.value = activeMastodonUserObjectID
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
return controller
|
||||
}()
|
||||
super.init()
|
||||
// end init
|
||||
|
||||
authentications
|
||||
mastodonAuthenticationFetchedResultsController.delegate = self
|
||||
|
||||
$authentications
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] authentications in
|
||||
guard let self = self else { return }
|
||||
|
@ -58,7 +59,10 @@ final class AccountListViewModel {
|
|||
|
||||
var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
|
||||
snapshot.appendSections([.main])
|
||||
snapshot.appendItems(authentications, toSection: .main)
|
||||
let authenticationItems: [Item] = authentications.map {
|
||||
Item.authentication(record: $0)
|
||||
}
|
||||
snapshot.appendItems(authenticationItems, toSection: .main)
|
||||
snapshot.appendItems([.addAccount], toSection: .main)
|
||||
|
||||
diffableDataSource.apply(snapshot) {
|
||||
|
@ -76,7 +80,7 @@ extension AccountListViewModel {
|
|||
}
|
||||
|
||||
enum Item: Hashable {
|
||||
case authentication(objectID: NSManagedObjectID)
|
||||
case authentication(record: ManagedObjectRecord<MastodonAuthentication>)
|
||||
case addAccount
|
||||
}
|
||||
|
||||
|
@ -86,14 +90,17 @@ extension AccountListViewModel {
|
|||
) {
|
||||
diffableDataSource = UITableViewDiffableDataSource(tableView: tableView) { tableView, indexPath, item in
|
||||
switch item {
|
||||
case .authentication(let objectID):
|
||||
let authentication = managedObjectContext.object(with: objectID) as! MastodonAuthentication
|
||||
case .authentication(let record):
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: AccountListTableViewCell.self), for: indexPath) as! AccountListTableViewCell
|
||||
if let authentication = record.object(in: managedObjectContext),
|
||||
let activeAuthentication = self.authContext.mastodonAuthenticationBox.authenticationRecord.object(in: managedObjectContext)
|
||||
{
|
||||
AccountListViewModel.configure(
|
||||
cell: cell,
|
||||
authentication: authentication,
|
||||
activeMastodonUserObjectID: self.activeMastodonUserObjectID.eraseToAnyPublisher()
|
||||
activeAuthentication: activeAuthentication
|
||||
)
|
||||
}
|
||||
return cell
|
||||
case .addAccount:
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: AddAccountTableViewCell.self), for: indexPath) as! AddAccountTableViewCell
|
||||
|
@ -109,7 +116,7 @@ extension AccountListViewModel {
|
|||
static func configure(
|
||||
cell: AccountListTableViewCell,
|
||||
authentication: MastodonAuthentication,
|
||||
activeMastodonUserObjectID: AnyPublisher<NSManagedObjectID?, Never>
|
||||
activeAuthentication: MastodonAuthentication
|
||||
) {
|
||||
let user = authentication.user
|
||||
|
||||
|
@ -138,19 +145,14 @@ extension AccountListViewModel {
|
|||
cell.badgeButton.setBadge(number: count)
|
||||
|
||||
// checkmark
|
||||
activeMastodonUserObjectID
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { objectID in
|
||||
let isCurrentUser = user.objectID == objectID
|
||||
let isActive = activeAuthentication.userID == authentication.userID
|
||||
cell.tintColor = .label
|
||||
cell.checkmarkImageView.isHidden = !isCurrentUser
|
||||
if isCurrentUser {
|
||||
cell.checkmarkImageView.isHidden = !isActive
|
||||
if isActive {
|
||||
cell.accessibilityTraits.insert(.selected)
|
||||
} else {
|
||||
cell.accessibilityTraits.remove(.selected)
|
||||
}
|
||||
}
|
||||
.store(in: &cell.disposeBag)
|
||||
|
||||
cell.accessibilityLabel = [
|
||||
cell.nameLabel.text,
|
||||
|
@ -161,3 +163,21 @@ extension AccountListViewModel {
|
|||
.joined(separator: " ")
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - NSFetchedResultsControllerDelegate
|
||||
extension AccountListViewModel: NSFetchedResultsControllerDelegate {
|
||||
|
||||
public func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
|
||||
os_log("%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
|
||||
}
|
||||
|
||||
public func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
|
||||
guard controller === mastodonAuthenticationFetchedResultsController else {
|
||||
assertionFailure()
|
||||
return
|
||||
}
|
||||
|
||||
authentications = mastodonAuthenticationFetchedResultsController.fetchedObjects?.compactMap { $0.asRecrod } ?? []
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@ final class AccountListViewController: UIViewController, NeedsDependency {
|
|||
weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } }
|
||||
|
||||
var disposeBag = Set<AnyCancellable>()
|
||||
private(set) lazy var viewModel = AccountListViewModel(context: context)
|
||||
var viewModel: AccountListViewModel!
|
||||
|
||||
private(set) lazy var addBarButtonItem: UIBarButtonItem = {
|
||||
let barButtonItem = UIBarButtonItem(
|
||||
|
@ -64,7 +64,10 @@ extension AccountListViewController: PanModalPresentable {
|
|||
return .contentHeight(CGFloat(height))
|
||||
}
|
||||
|
||||
let count = viewModel.context.authenticationService.mastodonAuthentications.value.count + 1
|
||||
let request = MastodonAuthentication.sortedFetchRequest
|
||||
let authenticationCount = (try? context.managedObjectContext.count(for: request)) ?? 0
|
||||
|
||||
let count = authenticationCount + 1
|
||||
let height = calculateHeight(of: count)
|
||||
return .contentHeight(height)
|
||||
}
|
||||
|
@ -174,16 +177,14 @@ extension AccountListViewController: UITableViewDelegate {
|
|||
guard let item = diffableDataSource.itemIdentifier(for: indexPath) else { return }
|
||||
|
||||
switch item {
|
||||
case .authentication(let objectID):
|
||||
case .authentication(let record):
|
||||
assert(Thread.isMainThread)
|
||||
let authentication = context.managedObjectContext.object(with: objectID) as! MastodonAuthentication
|
||||
context.authenticationService.activeMastodonUser(domain: authentication.domain, userID: authentication.userID)
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] result in
|
||||
guard let self = self else { return }
|
||||
guard let authentication = record.object(in: context.managedObjectContext) else { return }
|
||||
Task { @MainActor in
|
||||
let isActive = try await context.authenticationService.activeMastodonUser(domain: authentication.domain, userID: authentication.userID)
|
||||
guard isActive else { return }
|
||||
self.coordinator.setup()
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
} // end Task
|
||||
case .addAccount:
|
||||
// TODO: add dismiss entry for welcome scene
|
||||
coordinator.present(scene: .welcome, from: self, transition: .modal(animated: true, completion: nil))
|
||||
|
|
|
@ -134,11 +134,6 @@ extension AutoCompleteViewModel.State {
|
|||
return
|
||||
}
|
||||
|
||||
guard let authenticationBox = viewModel.context.authenticationService.activeMastodonAuthenticationBox.value else {
|
||||
await enter(state: Fail.self)
|
||||
return
|
||||
}
|
||||
|
||||
let searchText = viewModel.inputText.value
|
||||
let searchType = AutoCompleteViewModel.SearchType(inputText: searchText) ?? .default
|
||||
|
||||
|
@ -154,7 +149,7 @@ extension AutoCompleteViewModel.State {
|
|||
do {
|
||||
let response = try await viewModel.context.apiService.search(
|
||||
query: query,
|
||||
authenticationBox: authenticationBox
|
||||
authenticationBox: viewModel.authContext.mastodonAuthenticationBox
|
||||
)
|
||||
|
||||
await enter(state: Idle.self)
|
||||
|
|
|
@ -17,6 +17,7 @@ final class AutoCompleteViewModel {
|
|||
|
||||
// input
|
||||
let context: AppContext
|
||||
let authContext: AuthContext
|
||||
public let inputText = CurrentValueSubject<String, Never>("") // contains "@" or "#" prefix
|
||||
public let symbolBoundingRect = CurrentValueSubject<CGRect, Never>(.zero)
|
||||
public let customEmojiViewModel = CurrentValueSubject<EmojiService.CustomEmojiViewModel?, Never>(nil)
|
||||
|
@ -36,8 +37,9 @@ final class AutoCompleteViewModel {
|
|||
return stateMachine
|
||||
}()
|
||||
|
||||
init(context: AppContext) {
|
||||
init(context: AppContext, authContext: AuthContext) {
|
||||
self.context = context
|
||||
self.authContext = authContext
|
||||
|
||||
autoCompleteItems
|
||||
.receive(on: DispatchQueue.main)
|
||||
|
|
|
@ -137,7 +137,7 @@ final class ComposeViewController: UIViewController, NeedsDependency {
|
|||
|
||||
private(set) lazy var autoCompleteViewController: AutoCompleteViewController = {
|
||||
let viewController = AutoCompleteViewController()
|
||||
viewController.viewModel = AutoCompleteViewModel(context: context)
|
||||
viewController.viewModel = AutoCompleteViewModel(context: context, authContext: viewModel.authContext)
|
||||
viewController.delegate = self
|
||||
viewController.viewModel.customEmojiViewModel.value = viewModel.customEmojiViewModel
|
||||
return viewController
|
||||
|
|
|
@ -29,8 +29,11 @@ final class ComposeViewModel: NSObject {
|
|||
// input
|
||||
let context: AppContext
|
||||
let composeKind: ComposeStatusSection.ComposeKind
|
||||
let authenticationBox: MastodonAuthenticationBox
|
||||
let authContext: AuthContext
|
||||
|
||||
var authenticationBox: MastodonAuthenticationBox {
|
||||
authContext.mastodonAuthenticationBox
|
||||
}
|
||||
|
||||
@Published var isPollComposing = false
|
||||
@Published var isCustomEmojiComposing = false
|
||||
|
@ -116,11 +119,12 @@ final class ComposeViewModel: NSObject {
|
|||
init(
|
||||
context: AppContext,
|
||||
composeKind: ComposeStatusSection.ComposeKind,
|
||||
authenticationBox: MastodonAuthenticationBox
|
||||
authContext: AuthContext
|
||||
) {
|
||||
self.context = context
|
||||
self.composeKind = composeKind
|
||||
self.authenticationBox = authenticationBox
|
||||
self.authContext = authContext
|
||||
|
||||
self.title = {
|
||||
switch composeKind {
|
||||
case .post, .hashtag, .mention: return L10n.Scene.Compose.Title.newPost
|
||||
|
@ -130,8 +134,7 @@ final class ComposeViewModel: NSObject {
|
|||
self.selectedStatusVisibility = {
|
||||
// default private when user locked
|
||||
var visibility: ComposeToolbarView.VisibilitySelectionType = {
|
||||
guard let authenticationBox = context.authenticationService.activeMastodonAuthenticationBox.value,
|
||||
let author = authenticationBox.authenticationRecord.object(in: context.managedObjectContext)?.user
|
||||
guard let author = authContext.mastodonAuthenticationBox.authenticationRecord.object(in: context.managedObjectContext)?.user
|
||||
else {
|
||||
return .public
|
||||
}
|
||||
|
@ -168,15 +171,12 @@ final class ComposeViewModel: NSObject {
|
|||
self.instanceConfiguration = {
|
||||
var configuration: Mastodon.Entity.Instance.Configuration? = nil
|
||||
context.managedObjectContext.performAndWait {
|
||||
guard let authentication = authenticationBox.authenticationRecord.object(in: context.managedObjectContext)
|
||||
else {
|
||||
return
|
||||
}
|
||||
guard let authentication = authContext.mastodonAuthenticationBox.authenticationRecord.object(in: context.managedObjectContext) else { return }
|
||||
configuration = authentication.instance?.configuration
|
||||
}
|
||||
return configuration
|
||||
}()
|
||||
self.customEmojiViewModel = context.emojiService.dequeueCustomEmojiViewModel(for: authenticationBox.domain)
|
||||
self.customEmojiViewModel = context.emojiService.dequeueCustomEmojiViewModel(for: authContext.mastodonAuthenticationBox.domain)
|
||||
super.init()
|
||||
// end init
|
||||
|
||||
|
|
|
@ -116,6 +116,11 @@ extension DiscoveryCommunityViewController {
|
|||
|
||||
}
|
||||
|
||||
// MARK: - AuthContextProvider
|
||||
extension DiscoveryCommunityViewController: AuthContextProvider {
|
||||
var authContext: AuthContext { viewModel.authContext }
|
||||
}
|
||||
|
||||
// MARK: - UITableViewDelegate
|
||||
extension DiscoveryCommunityViewController: UITableViewDelegate, AutoGenerateTableViewDelegate {
|
||||
// sourcery:inline:CommunityViewController.AutoGenerateTableViewDelegate
|
||||
|
|
|
@ -18,6 +18,7 @@ extension DiscoveryCommunityViewModel {
|
|||
tableView: tableView,
|
||||
context: context,
|
||||
configuration: StatusSection.Configuration(
|
||||
authContext: authContext,
|
||||
statusTableViewCellDelegate: statusTableViewCellDelegate,
|
||||
timelineMiddleLoaderTableViewCellDelegate: nil,
|
||||
filterContext: .none,
|
||||
|
|
|
@ -136,11 +136,6 @@ extension DiscoveryCommunityViewModel.State {
|
|||
break
|
||||
}
|
||||
|
||||
guard let authenticationBox = viewModel.context.authenticationService.activeMastodonAuthenticationBox.value else {
|
||||
stateMachine.enter(Fail.self)
|
||||
return
|
||||
}
|
||||
|
||||
let maxID = self.maxID
|
||||
let isReloading = maxID == nil
|
||||
|
||||
|
@ -156,7 +151,7 @@ extension DiscoveryCommunityViewModel.State {
|
|||
minID: nil,
|
||||
limit: 20
|
||||
),
|
||||
authenticationBox: authenticationBox
|
||||
authenticationBox: viewModel.authContext.mastodonAuthenticationBox
|
||||
)
|
||||
|
||||
let newMaxID = response.link?.maxID
|
||||
|
|
|
@ -22,6 +22,7 @@ final class DiscoveryCommunityViewModel {
|
|||
|
||||
// input
|
||||
let context: AppContext
|
||||
let authContext: AuthContext
|
||||
let viewDidAppeared = PassthroughSubject<Void, Never>()
|
||||
let statusFetchedResultsController: StatusFetchedResultsController
|
||||
let listBatchFetchViewModel = ListBatchFetchViewModel()
|
||||
|
@ -43,20 +44,15 @@ final class DiscoveryCommunityViewModel {
|
|||
|
||||
let didLoadLatest = PassthroughSubject<Void, Never>()
|
||||
|
||||
init(context: AppContext) {
|
||||
init(context: AppContext, authContext: AuthContext) {
|
||||
self.context = context
|
||||
self.authContext = authContext
|
||||
self.statusFetchedResultsController = StatusFetchedResultsController(
|
||||
managedObjectContext: context.managedObjectContext,
|
||||
domain: nil,
|
||||
domain: authContext.mastodonAuthenticationBox.domain,
|
||||
additionalTweetPredicate: nil
|
||||
)
|
||||
// end init
|
||||
|
||||
context.authenticationService.activeMastodonAuthentication
|
||||
.map { $0?.domain }
|
||||
.assign(to: \.domain, on: statusFetchedResultsController)
|
||||
.store(in: &disposeBag)
|
||||
|
||||
}
|
||||
|
||||
deinit {
|
||||
|
|
|
@ -26,10 +26,7 @@ public class DiscoveryViewController: TabmanViewController, NeedsDependency {
|
|||
weak var context: AppContext! { willSet { precondition(!isViewLoaded) } }
|
||||
weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } }
|
||||
|
||||
private(set) lazy var viewModel = DiscoveryViewModel(
|
||||
context: context,
|
||||
coordinator: coordinator
|
||||
)
|
||||
var viewModel: DiscoveryViewModel!
|
||||
|
||||
private(set) lazy var buttonBar: TMBar.ButtonBar = {
|
||||
let buttonBar = TMBar.ButtonBar()
|
||||
|
|
|
@ -18,6 +18,7 @@ final class DiscoveryViewModel {
|
|||
|
||||
// input
|
||||
let context: AppContext
|
||||
let authContext: AuthContext
|
||||
let discoveryPostsViewController: DiscoveryPostsViewController
|
||||
let discoveryHashtagsViewController: DiscoveryHashtagsViewController
|
||||
let discoveryNewsViewController: DiscoveryNewsViewController
|
||||
|
@ -26,41 +27,43 @@ final class DiscoveryViewModel {
|
|||
|
||||
@Published var viewControllers: [ScrollViewContainer & PageViewController]
|
||||
|
||||
init(context: AppContext, coordinator: SceneCoordinator) {
|
||||
init(context: AppContext, coordinator: SceneCoordinator, authContext: AuthContext) {
|
||||
self.context = context
|
||||
self.authContext = authContext
|
||||
|
||||
func setupDependency(_ needsDependency: NeedsDependency) {
|
||||
needsDependency.context = context
|
||||
needsDependency.coordinator = coordinator
|
||||
}
|
||||
|
||||
self.context = context
|
||||
discoveryPostsViewController = {
|
||||
let viewController = DiscoveryPostsViewController()
|
||||
setupDependency(viewController)
|
||||
viewController.viewModel = DiscoveryPostsViewModel(context: context)
|
||||
viewController.viewModel = DiscoveryPostsViewModel(context: context, authContext: authContext)
|
||||
return viewController
|
||||
}()
|
||||
discoveryHashtagsViewController = {
|
||||
let viewController = DiscoveryHashtagsViewController()
|
||||
setupDependency(viewController)
|
||||
viewController.viewModel = DiscoveryHashtagsViewModel(context: context)
|
||||
viewController.viewModel = DiscoveryHashtagsViewModel(context: context, authContext: authContext)
|
||||
return viewController
|
||||
}()
|
||||
discoveryNewsViewController = {
|
||||
let viewController = DiscoveryNewsViewController()
|
||||
setupDependency(viewController)
|
||||
viewController.viewModel = DiscoveryNewsViewModel(context: context)
|
||||
viewController.viewModel = DiscoveryNewsViewModel(context: context, authContext: authContext)
|
||||
return viewController
|
||||
}()
|
||||
discoveryCommunityViewController = {
|
||||
let viewController = DiscoveryCommunityViewController()
|
||||
setupDependency(viewController)
|
||||
viewController.viewModel = DiscoveryCommunityViewModel(context: context)
|
||||
viewController.viewModel = DiscoveryCommunityViewModel(context: context, authContext: authContext)
|
||||
return viewController
|
||||
}()
|
||||
discoveryForYouViewController = {
|
||||
let viewController = DiscoveryForYouViewController()
|
||||
setupDependency(viewController)
|
||||
viewController.viewModel = DiscoveryForYouViewModel(context: context)
|
||||
viewController.viewModel = DiscoveryForYouViewModel(context: context, authContext: authContext)
|
||||
return viewController
|
||||
}()
|
||||
self.viewControllers = [
|
||||
|
|
|
@ -101,6 +101,11 @@ extension DiscoveryForYouViewController {
|
|||
|
||||
}
|
||||
|
||||
// MARK: - AuthContextProvider
|
||||
extension DiscoveryForYouViewController: AuthContextProvider {
|
||||
var authContext: AuthContext { viewModel.authContext }
|
||||
}
|
||||
|
||||
// MARK: - UITableViewDelegate
|
||||
extension DiscoveryForYouViewController: UITableViewDelegate {
|
||||
|
||||
|
@ -110,9 +115,10 @@ extension DiscoveryForYouViewController: UITableViewDelegate {
|
|||
guard let user = record.object(in: context.managedObjectContext) else { return }
|
||||
let profileViewModel = CachedProfileViewModel(
|
||||
context: context,
|
||||
authContext: viewModel.authContext,
|
||||
mastodonUser: user
|
||||
)
|
||||
coordinator.present(
|
||||
_ = coordinator.present(
|
||||
scene: .profile(viewModel: profileViewModel),
|
||||
from: self,
|
||||
transition: .show
|
||||
|
@ -128,15 +134,13 @@ extension DiscoveryForYouViewController: ProfileCardTableViewCellDelegate {
|
|||
profileCardView: ProfileCardView,
|
||||
relationshipButtonDidPressed button: ProfileRelationshipActionButton
|
||||
) {
|
||||
guard let authenticationBox = viewModel.context.authenticationService.activeMastodonAuthenticationBox.value else { return }
|
||||
guard let indexPath = tableView.indexPath(for: cell) else { return }
|
||||
guard case let .user(record) = viewModel.diffableDataSource?.itemIdentifier(for: indexPath) else { return }
|
||||
|
||||
Task {
|
||||
try await DataSourceFacade.responseToUserFollowAction(
|
||||
dependency: self,
|
||||
user: record,
|
||||
authenticationBox: authenticationBox
|
||||
user: record
|
||||
)
|
||||
} // end Task
|
||||
}
|
||||
|
@ -157,9 +161,9 @@ extension DiscoveryForYouViewController: ProfileCardTableViewCellDelegate {
|
|||
return
|
||||
}
|
||||
|
||||
let familiarFollowersViewModel = FamiliarFollowersViewModel(context: context)
|
||||
let familiarFollowersViewModel = FamiliarFollowersViewModel(context: context, authContext: authContext)
|
||||
familiarFollowersViewModel.familiarFollowers = familiarFollowers
|
||||
coordinator.present(
|
||||
_ = coordinator.present(
|
||||
scene: .familiarFollowers(viewModel: familiarFollowersViewModel),
|
||||
from: self,
|
||||
transition: .show
|
||||
|
|
|
@ -19,6 +19,7 @@ extension DiscoveryForYouViewModel {
|
|||
tableView: tableView,
|
||||
context: context,
|
||||
configuration: DiscoverySection.Configuration(
|
||||
authContext: authContext,
|
||||
profileCardTableViewCellDelegate: profileCardTableViewCellDelegate,
|
||||
familiarFollowers: $familiarFollowers
|
||||
)
|
||||
|
|
|
@ -20,6 +20,7 @@ final class DiscoveryForYouViewModel {
|
|||
|
||||
// input
|
||||
let context: AppContext
|
||||
let authContext: AuthContext
|
||||
let userFetchedResultsController: UserFetchedResultsController
|
||||
|
||||
@MainActor
|
||||
|
@ -30,19 +31,15 @@ final class DiscoveryForYouViewModel {
|
|||
var diffableDataSource: UITableViewDiffableDataSource<DiscoverySection, DiscoveryItem>?
|
||||
let didLoadLatest = PassthroughSubject<Void, Never>()
|
||||
|
||||
init(context: AppContext) {
|
||||
init(context: AppContext, authContext: AuthContext) {
|
||||
self.context = context
|
||||
self.authContext = authContext
|
||||
self.userFetchedResultsController = UserFetchedResultsController(
|
||||
managedObjectContext: context.managedObjectContext,
|
||||
domain: nil,
|
||||
domain: authContext.mastodonAuthenticationBox.domain,
|
||||
additionalPredicate: nil
|
||||
)
|
||||
// end init
|
||||
|
||||
context.authenticationService.activeMastodonAuthenticationBox
|
||||
.map { $0?.domain }
|
||||
.assign(to: \.domain, on: userFetchedResultsController)
|
||||
.store(in: &disposeBag)
|
||||
}
|
||||
|
||||
deinit {
|
||||
|
@ -59,16 +56,12 @@ extension DiscoveryForYouViewModel {
|
|||
isFetching = true
|
||||
defer { isFetching = false }
|
||||
|
||||
guard let authenticationBox = context.authenticationService.activeMastodonAuthenticationBox.value else {
|
||||
throw APIService.APIError.implicit(.badRequest)
|
||||
}
|
||||
|
||||
do {
|
||||
let userIDs = try await fetchSuggestionAccounts()
|
||||
|
||||
let _familiarFollowersResponse = try? await context.apiService.familiarFollowers(
|
||||
query: .init(ids: userIDs),
|
||||
authenticationBox: authenticationBox
|
||||
authenticationBox: authContext.mastodonAuthenticationBox
|
||||
)
|
||||
familiarFollowers = _familiarFollowersResponse?.value ?? []
|
||||
userFetchedResultsController.userIDs = userIDs
|
||||
|
@ -78,14 +71,10 @@ extension DiscoveryForYouViewModel {
|
|||
}
|
||||
|
||||
private func fetchSuggestionAccounts() async throws -> [Mastodon.Entity.Account.ID] {
|
||||
guard let authenticationBox = context.authenticationService.activeMastodonAuthenticationBox.value else {
|
||||
throw APIService.APIError.implicit(.badRequest)
|
||||
}
|
||||
|
||||
do {
|
||||
let response = try await context.apiService.suggestionAccountV2(
|
||||
query: nil,
|
||||
authenticationBox: authenticationBox
|
||||
authenticationBox: authContext.mastodonAuthenticationBox
|
||||
)
|
||||
let userIDs = response.value.map { $0.account.id }
|
||||
return userIDs
|
||||
|
@ -93,7 +82,7 @@ extension DiscoveryForYouViewModel {
|
|||
// fallback V1
|
||||
let response = try await context.apiService.suggestionAccount(
|
||||
query: nil,
|
||||
authenticationBox: authenticationBox
|
||||
authenticationBox: authContext.mastodonAuthenticationBox
|
||||
)
|
||||
let userIDs = response.value.map { $0.id }
|
||||
return userIDs
|
||||
|
|
|
@ -107,7 +107,7 @@ extension DiscoveryHashtagsViewController: UITableViewDelegate {
|
|||
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): \(indexPath)")
|
||||
guard case let .hashtag(tag) = viewModel.diffableDataSource?.itemIdentifier(for: indexPath) else { return }
|
||||
let hashtagTimelineViewModel = HashtagTimelineViewModel(context: context, hashtag: tag.name)
|
||||
let hashtagTimelineViewModel = HashtagTimelineViewModel(context: context, authContext: viewModel.authContext, hashtag: tag.name)
|
||||
coordinator.present(
|
||||
scene: .hashtagTimeline(viewModel: hashtagTimelineViewModel),
|
||||
from: self,
|
||||
|
@ -217,7 +217,7 @@ extension DiscoveryHashtagsViewController: TableViewControllerNavigateable {
|
|||
guard let item = diffableDataSource.itemIdentifier(for: indexPathForSelectedRow) else { return }
|
||||
|
||||
guard case let .hashtag(tag) = item else { return }
|
||||
let hashtagTimelineViewModel = HashtagTimelineViewModel(context: context, hashtag: tag.name)
|
||||
let hashtagTimelineViewModel = HashtagTimelineViewModel(context: context, authContext: viewModel.authContext, hashtag: tag.name)
|
||||
coordinator.present(
|
||||
scene: .hashtagTimeline(viewModel: hashtagTimelineViewModel),
|
||||
from: self,
|
||||
|
|
|
@ -15,7 +15,7 @@ extension DiscoveryHashtagsViewModel {
|
|||
diffableDataSource = DiscoverySection.diffableDataSource(
|
||||
tableView: tableView,
|
||||
context: context,
|
||||
configuration: DiscoverySection.Configuration()
|
||||
configuration: DiscoverySection.Configuration(authContext: authContext)
|
||||
)
|
||||
|
||||
var snapshot = NSDiffableDataSourceSnapshot<DiscoverySection, DiscoveryItem>()
|
||||
|
|
|
@ -22,26 +22,22 @@ final class DiscoveryHashtagsViewModel {
|
|||
|
||||
// input
|
||||
let context: AppContext
|
||||
let authContext: AuthContext
|
||||
let viewDidAppeared = PassthroughSubject<Void, Never>()
|
||||
|
||||
// output
|
||||
var diffableDataSource: UITableViewDiffableDataSource<DiscoverySection, DiscoveryItem>?
|
||||
@Published var hashtags: [Mastodon.Entity.Tag] = []
|
||||
|
||||
init(context: AppContext) {
|
||||
init(context: AppContext, authContext: AuthContext) {
|
||||
self.context = context
|
||||
self.authContext = authContext
|
||||
// end init
|
||||
|
||||
Publishers.CombineLatest(
|
||||
context.authenticationService.activeMastodonAuthenticationBox,
|
||||
viewDidAppeared
|
||||
)
|
||||
.compactMap { authenticationBox, _ -> MastodonAuthenticationBox? in
|
||||
return authenticationBox
|
||||
}
|
||||
.throttle(for: 3, scheduler: DispatchQueue.main, latest: true)
|
||||
.asyncMap { authenticationBox in
|
||||
try await context.apiService.trendHashtags(domain: authenticationBox.domain, query: nil)
|
||||
try await context.apiService.trendHashtags(domain: authContext.mastodonAuthenticationBox.domain, query: nil)
|
||||
}
|
||||
.retry(3)
|
||||
.map { response in Result<Mastodon.Response.Content<[Mastodon.Entity.Tag]>, Error> { response } }
|
||||
|
@ -69,8 +65,7 @@ extension DiscoveryHashtagsViewModel {
|
|||
|
||||
@MainActor
|
||||
func fetch() async throws {
|
||||
guard let authenticationBox = context.authenticationService.activeMastodonAuthenticationBox.value else { return }
|
||||
let response = try await context.apiService.trendHashtags(domain: authenticationBox.domain, query: nil)
|
||||
let response = try await context.apiService.trendHashtags(domain: authContext.mastodonAuthenticationBox.domain, query: nil)
|
||||
hashtags = response.value.filter { !$0.name.isEmpty }
|
||||
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): fetch tags: \(response.value.count)")
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ extension DiscoveryNewsViewModel {
|
|||
diffableDataSource = DiscoverySection.diffableDataSource(
|
||||
tableView: tableView,
|
||||
context: context,
|
||||
configuration: DiscoverySection.Configuration()
|
||||
configuration: DiscoverySection.Configuration(authContext: authContext)
|
||||
)
|
||||
|
||||
stateMachine.enter(State.Reloading.self)
|
||||
|
|
|
@ -137,18 +137,13 @@ extension DiscoveryNewsViewModel.State {
|
|||
break
|
||||
}
|
||||
|
||||
guard let authenticationBox = viewModel.context.authenticationService.activeMastodonAuthenticationBox.value else {
|
||||
stateMachine.enter(Fail.self)
|
||||
return
|
||||
}
|
||||
|
||||
let offset = self.offset
|
||||
let isReloading = offset == nil
|
||||
|
||||
Task {
|
||||
do {
|
||||
let response = try await viewModel.context.apiService.trendLinks(
|
||||
domain: authenticationBox.domain,
|
||||
domain: viewModel.authContext.mastodonAuthenticationBox.domain,
|
||||
query: Mastodon.API.Trends.StatusQuery(
|
||||
offset: offset,
|
||||
limit: nil
|
||||
|
|
|
@ -20,6 +20,7 @@ final class DiscoveryNewsViewModel {
|
|||
|
||||
// input
|
||||
let context: AppContext
|
||||
let authContext: AuthContext
|
||||
let listBatchFetchViewModel = ListBatchFetchViewModel()
|
||||
|
||||
// output
|
||||
|
@ -41,8 +42,9 @@ final class DiscoveryNewsViewModel {
|
|||
let didLoadLatest = PassthroughSubject<Void, Never>()
|
||||
@Published var isServerSupportEndpoint = true
|
||||
|
||||
init(context: AppContext) {
|
||||
init(context: AppContext, authContext: AuthContext) {
|
||||
self.context = context
|
||||
self.authContext = authContext
|
||||
// end init
|
||||
|
||||
Task {
|
||||
|
@ -59,11 +61,9 @@ final class DiscoveryNewsViewModel {
|
|||
|
||||
extension DiscoveryNewsViewModel {
|
||||
func checkServerEndpoint() async {
|
||||
guard let authenticationBox = context.authenticationService.activeMastodonAuthenticationBox.value else { return }
|
||||
|
||||
do {
|
||||
_ = try await context.apiService.trendLinks(
|
||||
domain: authenticationBox.domain,
|
||||
domain: authContext.mastodonAuthenticationBox.domain,
|
||||
query: .init(offset: nil, limit: nil)
|
||||
)
|
||||
} catch let error as Mastodon.API.Error where error.httpResponseStatus.code == 404 {
|
||||
|
|
|
@ -128,6 +128,11 @@ extension DiscoveryPostsViewController {
|
|||
|
||||
}
|
||||
|
||||
// MARK: - AuthContextProvider
|
||||
extension DiscoveryPostsViewController: AuthContextProvider {
|
||||
var authContext: AuthContext { viewModel.authContext }
|
||||
}
|
||||
|
||||
// MARK: - UITableViewDelegate
|
||||
extension DiscoveryPostsViewController: UITableViewDelegate, AutoGenerateTableViewDelegate {
|
||||
// sourcery:inline:DiscoveryPostsViewController.AutoGenerateTableViewDelegate
|
||||
|
|
|
@ -18,6 +18,7 @@ extension DiscoveryPostsViewModel {
|
|||
tableView: tableView,
|
||||
context: context,
|
||||
configuration: StatusSection.Configuration(
|
||||
authContext: authContext,
|
||||
statusTableViewCellDelegate: statusTableViewCellDelegate,
|
||||
timelineMiddleLoaderTableViewCellDelegate: nil,
|
||||
filterContext: .none,
|
||||
|
|
|
@ -137,18 +137,13 @@ extension DiscoveryPostsViewModel.State {
|
|||
break
|
||||
}
|
||||
|
||||
guard let authenticationBox = viewModel.context.authenticationService.activeMastodonAuthenticationBox.value else {
|
||||
stateMachine.enter(Fail.self)
|
||||
return
|
||||
}
|
||||
|
||||
let offset = self.offset
|
||||
let isReloading = offset == nil
|
||||
|
||||
Task {
|
||||
do {
|
||||
let response = try await viewModel.context.apiService.trendStatuses(
|
||||
domain: authenticationBox.domain,
|
||||
domain: viewModel.authContext.mastodonAuthenticationBox.domain,
|
||||
query: Mastodon.API.Trends.StatusQuery(
|
||||
offset: offset,
|
||||
limit: nil
|
||||
|
|
|
@ -20,6 +20,7 @@ final class DiscoveryPostsViewModel {
|
|||
|
||||
// input
|
||||
let context: AppContext
|
||||
let authContext: AuthContext
|
||||
let statusFetchedResultsController: StatusFetchedResultsController
|
||||
let listBatchFetchViewModel = ListBatchFetchViewModel()
|
||||
|
||||
|
@ -41,20 +42,16 @@ final class DiscoveryPostsViewModel {
|
|||
let didLoadLatest = PassthroughSubject<Void, Never>()
|
||||
@Published var isServerSupportEndpoint = true
|
||||
|
||||
init(context: AppContext) {
|
||||
init(context: AppContext, authContext: AuthContext) {
|
||||
self.context = context
|
||||
self.authContext = authContext
|
||||
self.statusFetchedResultsController = StatusFetchedResultsController(
|
||||
managedObjectContext: context.managedObjectContext,
|
||||
domain: nil,
|
||||
domain: authContext.mastodonAuthenticationBox.domain,
|
||||
additionalTweetPredicate: nil
|
||||
)
|
||||
// end init
|
||||
|
||||
context.authenticationService.activeMastodonAuthentication
|
||||
.map { $0?.domain }
|
||||
.assign(to: \.domain, on: statusFetchedResultsController)
|
||||
.store(in: &disposeBag)
|
||||
|
||||
Task {
|
||||
await checkServerEndpoint()
|
||||
} // end Task
|
||||
|
@ -68,11 +65,9 @@ final class DiscoveryPostsViewModel {
|
|||
|
||||
extension DiscoveryPostsViewModel {
|
||||
func checkServerEndpoint() async {
|
||||
guard let authenticationBox = context.authenticationService.activeMastodonAuthenticationBox.value else { return }
|
||||
|
||||
do {
|
||||
_ = try await context.apiService.trendStatuses(
|
||||
domain: authenticationBox.domain,
|
||||
domain: authContext.mastodonAuthenticationBox.domain,
|
||||
query: .init(offset: nil, limit: nil)
|
||||
)
|
||||
} catch let error as Mastodon.API.Error where error.httpResponseStatus.code == 404 {
|
||||
|
|
|
@ -166,17 +166,21 @@ extension HashtagTimelineViewController {
|
|||
|
||||
@objc private func composeBarButtonItemPressed(_ sender: UIBarButtonItem) {
|
||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
|
||||
guard let authenticationBox = context.authenticationService.activeMastodonAuthenticationBox.value else { return }
|
||||
let composeViewModel = ComposeViewModel(
|
||||
context: context,
|
||||
composeKind: .hashtag(hashtag: viewModel.hashtag),
|
||||
authenticationBox: authenticationBox
|
||||
authContext: viewModel.authContext
|
||||
)
|
||||
coordinator.present(scene: .compose(viewModel: composeViewModel), from: self, transition: .modal(animated: true, completion: nil))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - AuthContextProvider
|
||||
extension HashtagTimelineViewController: AuthContextProvider {
|
||||
var authContext: AuthContext { viewModel.authContext }
|
||||
}
|
||||
|
||||
// MARK: - UITableViewDelegate
|
||||
extension HashtagTimelineViewController: UITableViewDelegate, AutoGenerateTableViewDelegate {
|
||||
// sourcery:inline:HashtagTimelineViewController.AutoGenerateTableViewDelegate
|
||||
|
|
|
@ -20,6 +20,7 @@ extension HashtagTimelineViewModel {
|
|||
tableView: tableView,
|
||||
context: context,
|
||||
configuration: StatusSection.Configuration(
|
||||
authContext: authContext,
|
||||
statusTableViewCellDelegate: statusTableViewCellDelegate,
|
||||
timelineMiddleLoaderTableViewCellDelegate: nil,
|
||||
filterContext: .none,
|
||||
|
|
|
@ -135,12 +135,6 @@ extension HashtagTimelineViewModel.State {
|
|||
break
|
||||
}
|
||||
|
||||
guard let authenticationBox = viewModel.context.authenticationService.activeMastodonAuthenticationBox.value else {
|
||||
assertionFailure()
|
||||
stateMachine.enter(Fail.self)
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: only set large count when using Wi-Fi
|
||||
let maxID = self.maxID
|
||||
let isReloading = maxID == nil
|
||||
|
@ -148,10 +142,10 @@ extension HashtagTimelineViewModel.State {
|
|||
Task {
|
||||
do {
|
||||
let response = try await viewModel.context.apiService.hashtagTimeline(
|
||||
domain: authenticationBox.domain,
|
||||
domain: viewModel.authContext.mastodonAuthenticationBox.domain,
|
||||
maxID: maxID,
|
||||
hashtag: viewModel.hashtag,
|
||||
authenticationBox: authenticationBox
|
||||
authenticationBox: viewModel.authContext.mastodonAuthenticationBox
|
||||
)
|
||||
|
||||
let newMaxID: String? = {
|
||||
|
|
|
@ -26,6 +26,7 @@ final class HashtagTimelineViewModel {
|
|||
|
||||
// input
|
||||
let context: AppContext
|
||||
let authContext: AuthContext
|
||||
let fetchedResultsController: StatusFetchedResultsController
|
||||
let isFetchingLatestTimeline = CurrentValueSubject<Bool, Never>(false)
|
||||
let timelinePredicate = CurrentValueSubject<NSPredicate?, Never>(nil)
|
||||
|
@ -52,20 +53,16 @@ final class HashtagTimelineViewModel {
|
|||
}()
|
||||
lazy var loadOldestStateMachinePublisher = CurrentValueSubject<State?, Never>(nil)
|
||||
|
||||
init(context: AppContext, hashtag: String) {
|
||||
init(context: AppContext, authContext: AuthContext, hashtag: String) {
|
||||
self.context = context
|
||||
self.authContext = authContext
|
||||
self.hashtag = hashtag
|
||||
self.fetchedResultsController = StatusFetchedResultsController(
|
||||
managedObjectContext: context.managedObjectContext,
|
||||
domain: nil,
|
||||
domain: authContext.mastodonAuthenticationBox.domain,
|
||||
additionalTweetPredicate: nil
|
||||
)
|
||||
// end init
|
||||
|
||||
context.authenticationService.activeMastodonAuthenticationBox
|
||||
.map { $0?.domain }
|
||||
.assign(to: \.domain, on: fetchedResultsController)
|
||||
.store(in: &disposeBag)
|
||||
}
|
||||
|
||||
deinit {
|
||||
|
|
|
@ -81,8 +81,11 @@ extension HomeTimelineViewController {
|
|||
},
|
||||
UIAction(title: "Account Recommend", image: UIImage(systemName: "human"), attributes: []) { [weak self] action in
|
||||
guard let self = self else { return }
|
||||
let suggestionAccountViewModel = SuggestionAccountViewModel(context: self.context)
|
||||
self.coordinator.present(
|
||||
let suggestionAccountViewModel = SuggestionAccountViewModel(
|
||||
context: self.context,
|
||||
authContext: self.viewModel.authContext
|
||||
)
|
||||
_ = self.coordinator.present(
|
||||
scene: .suggestionAccount(viewModel: suggestionAccountViewModel),
|
||||
from: self,
|
||||
transition: .modal(animated: true, completion: nil)
|
||||
|
@ -150,7 +153,7 @@ extension HomeTimelineViewController {
|
|||
children: [
|
||||
UIAction(title: "Badge +1", image: UIImage(systemName: "app.badge.fill"), attributes: []) { [weak self] action in
|
||||
guard let self = self else { return }
|
||||
guard let accessToken = self.context.authenticationService.activeMastodonAuthentication.value?.userAccessToken else { return }
|
||||
let accessToken = self.viewModel.authContext.mastodonAuthenticationBox.userAuthorization.accessToken
|
||||
UserDefaults.shared.increaseNotificationCount(accessToken: accessToken)
|
||||
self.context.notificationService.applicationIconBadgeNeedsUpdate.send()
|
||||
},
|
||||
|
@ -333,7 +336,8 @@ extension HomeTimelineViewController {
|
|||
}
|
||||
|
||||
@objc private func showAccountList(_ sender: UIAction) {
|
||||
coordinator.present(scene: .accountList, from: self, transition: .modal(animated: true, completion: nil))
|
||||
let accountListViewModel = AccountListViewModel(context: context, authContext: viewModel.authContext)
|
||||
coordinator.present(scene: .accountList(viewModel: accountListViewModel), from: self, transition: .modal(animated: true, completion: nil))
|
||||
}
|
||||
|
||||
@objc private func showProfileAction(_ sender: UIAction) {
|
||||
|
@ -342,7 +346,7 @@ extension HomeTimelineViewController {
|
|||
let showAction = UIAlertAction(title: "Show", style: .default) { [weak self, weak alertController] _ in
|
||||
guard let self = self else { return }
|
||||
guard let textField = alertController?.textFields?.first else { return }
|
||||
let profileViewModel = RemoteProfileViewModel(context: self.context, userID: textField.text ?? "")
|
||||
let profileViewModel = RemoteProfileViewModel(context: self.context, authContext: self.viewModel.authContext, userID: textField.text ?? "")
|
||||
self.coordinator.present(scene: .profile(viewModel: profileViewModel), from: self, transition: .show)
|
||||
}
|
||||
alertController.addAction(showAction)
|
||||
|
@ -357,7 +361,7 @@ extension HomeTimelineViewController {
|
|||
let showAction = UIAlertAction(title: "Show", style: .default) { [weak self, weak alertController] _ in
|
||||
guard let self = self else { return }
|
||||
guard let textField = alertController?.textFields?.first else { return }
|
||||
let threadViewModel = RemoteThreadViewModel(context: self.context, statusID: textField.text ?? "")
|
||||
let threadViewModel = RemoteThreadViewModel(context: self.context, authContext: self.viewModel.authContext, statusID: textField.text ?? "")
|
||||
self.coordinator.present(scene: .thread(viewModel: threadViewModel), from: self, transition: .show)
|
||||
}
|
||||
alertController.addAction(showAction)
|
||||
|
@ -367,8 +371,6 @@ extension HomeTimelineViewController {
|
|||
}
|
||||
|
||||
private func showNotification(_ sender: UIAction, notificationType: Mastodon.Entity.Notification.NotificationType) {
|
||||
guard let authenticationBox = self.context.authenticationService.activeMastodonAuthenticationBox.value else { return }
|
||||
|
||||
let alertController = UIAlertController(title: "Enter notification ID", message: nil, preferredStyle: .alert)
|
||||
alertController.addTextField()
|
||||
|
||||
|
@ -380,7 +382,7 @@ extension HomeTimelineViewController {
|
|||
else { return }
|
||||
|
||||
let pushNotification = MastodonPushNotification(
|
||||
accessToken: authenticationBox.userAuthorization.accessToken,
|
||||
accessToken: self.viewModel.authContext.mastodonAuthenticationBox.userAuthorization.accessToken,
|
||||
notificationID: notificationID,
|
||||
notificationType: notificationType.rawValue,
|
||||
preferredLocale: nil,
|
||||
|
@ -393,7 +395,7 @@ extension HomeTimelineViewController {
|
|||
alertController.addAction(showAction)
|
||||
|
||||
// for multiple accounts debug
|
||||
let boxes = self.context.authenticationService.mastodonAuthenticationBoxes.value // already sorted
|
||||
let boxes = self.context.authenticationService.mastodonAuthenticationBoxes // already sorted
|
||||
if boxes.count >= 2 {
|
||||
let accessToken = boxes[1].userAuthorization.accessToken
|
||||
let showForSecondaryAction = UIAlertAction(title: "Show for Secondary", style: .default) { [weak self, weak alertController] _ in
|
||||
|
@ -420,12 +422,20 @@ extension HomeTimelineViewController {
|
|||
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
|
||||
alertController.addAction(cancelAction)
|
||||
|
||||
self.coordinator.present(scene: .alertController(alertController: alertController), from: self, transition: .alertController(animated: true, completion: nil))
|
||||
_ = self.coordinator.present(
|
||||
scene: .alertController(alertController: alertController),
|
||||
from: self,
|
||||
transition: .alertController(animated: true, completion: nil)
|
||||
)
|
||||
}
|
||||
|
||||
@objc private func showSettings(_ sender: UIAction) {
|
||||
guard let currentSetting = context.settingService.currentSetting.value else { return }
|
||||
let settingsViewModel = SettingsViewModel(context: context, setting: currentSetting)
|
||||
let settingsViewModel = SettingsViewModel(
|
||||
context: context,
|
||||
authContext: viewModel.authContext,
|
||||
setting: currentSetting
|
||||
)
|
||||
coordinator.present(
|
||||
scene: .settings(viewModel: settingsViewModel),
|
||||
from: self,
|
||||
|
|
|
@ -28,7 +28,7 @@ final class HomeTimelineViewController: UIViewController, NeedsDependency, Media
|
|||
weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } }
|
||||
|
||||
var disposeBag = Set<AnyCancellable>()
|
||||
private(set) lazy var viewModel = HomeTimelineViewModel(context: context)
|
||||
var viewModel: HomeTimelineViewModel!
|
||||
|
||||
let mediaPreviewTransitionController = MediaPreviewTransitionController()
|
||||
|
||||
|
@ -373,9 +373,9 @@ extension HomeTimelineViewController {
|
|||
extension HomeTimelineViewController {
|
||||
|
||||
@objc private func findPeopleButtonPressed(_ sender: PrimaryActionButton) {
|
||||
let suggestionAccountViewModel = SuggestionAccountViewModel(context: context)
|
||||
let suggestionAccountViewModel = SuggestionAccountViewModel(context: context, authContext: viewModel.authContext)
|
||||
suggestionAccountViewModel.delegate = viewModel
|
||||
coordinator.present(
|
||||
_ = coordinator.present(
|
||||
scene: .suggestionAccount(viewModel: suggestionAccountViewModel),
|
||||
from: self,
|
||||
transition: .modal(animated: true, completion: nil)
|
||||
|
@ -391,7 +391,7 @@ extension HomeTimelineViewController {
|
|||
@objc private func settingBarButtonItemPressed(_ sender: UIBarButtonItem) {
|
||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
|
||||
guard let setting = context.settingService.currentSetting.value else { return }
|
||||
let settingsViewModel = SettingsViewModel(context: context, setting: setting)
|
||||
let settingsViewModel = SettingsViewModel(context: context, authContext: viewModel.authContext, setting: setting)
|
||||
coordinator.present(scene: .settings(viewModel: settingsViewModel), from: self, transition: .modal(animated: true, completion: nil))
|
||||
}
|
||||
|
||||
|
@ -403,12 +403,8 @@ extension HomeTimelineViewController {
|
|||
}
|
||||
|
||||
@objc func signOutAction(_ sender: UIAction) {
|
||||
guard let authenticationBox = context.authenticationService.activeMastodonAuthenticationBox.value else {
|
||||
return
|
||||
}
|
||||
|
||||
Task { @MainActor in
|
||||
try await context.authenticationService.signOutMastodonUser(authenticationBox: authenticationBox)
|
||||
try await context.authenticationService.signOutMastodonUser(authenticationBox: viewModel.authContext.mastodonAuthenticationBox)
|
||||
self.coordinator.setup()
|
||||
}
|
||||
}
|
||||
|
@ -492,6 +488,11 @@ extension HomeTimelineViewController {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: - AuthContextProvider
|
||||
extension HomeTimelineViewController: AuthContextProvider {
|
||||
var authContext: AuthContext { viewModel.authContext }
|
||||
}
|
||||
|
||||
// MARK: - UITableViewDelegate
|
||||
extension HomeTimelineViewController: UITableViewDelegate, AutoGenerateTableViewDelegate {
|
||||
// sourcery:inline:HomeTimelineViewController.AutoGenerateTableViewDelegate
|
||||
|
|
|
@ -21,6 +21,7 @@ extension HomeTimelineViewModel {
|
|||
tableView: tableView,
|
||||
context: context,
|
||||
configuration: StatusSection.Configuration(
|
||||
authContext: authContext,
|
||||
statusTableViewCellDelegate: statusTableViewCellDelegate,
|
||||
timelineMiddleLoaderTableViewCellDelegate: timelineMiddleLoaderTableViewCellDelegate,
|
||||
filterContext: .home,
|
||||
|
|
|
@ -63,11 +63,6 @@ extension HomeTimelineViewModel.LoadLatestState {
|
|||
override func didEnter(from previousState: GKState?) {
|
||||
super.didEnter(from: previousState)
|
||||
guard let viewModel = viewModel, let stateMachine = stateMachine else { return }
|
||||
guard let activeMastodonAuthenticationBox = viewModel.context.authenticationService.activeMastodonAuthenticationBox.value else {
|
||||
// sign out when loading will enter here
|
||||
stateMachine.enter(Fail.self)
|
||||
return
|
||||
}
|
||||
|
||||
let latestFeedRecords = viewModel.fetchedResultsController.records.prefix(APIService.onceRequestStatusMaxCount)
|
||||
let parentManagedObjectContext = viewModel.fetchedResultsController.fetchedResultsController.managedObjectContext
|
||||
|
@ -85,7 +80,7 @@ extension HomeTimelineViewModel.LoadLatestState {
|
|||
|
||||
do {
|
||||
let response = try await viewModel.context.apiService.homeTimeline(
|
||||
authenticationBox: activeMastodonAuthenticationBox
|
||||
authenticationBox: viewModel.authContext.mastodonAuthenticationBox
|
||||
)
|
||||
|
||||
await enter(state: Idle.self)
|
||||
|
|
|
@ -64,11 +64,6 @@ extension HomeTimelineViewModel.LoadOldestState {
|
|||
super.didEnter(from: previousState)
|
||||
|
||||
guard let viewModel = viewModel, let stateMachine = stateMachine else { return }
|
||||
guard let activeMastodonAuthenticationBox = viewModel.context.authenticationService.activeMastodonAuthenticationBox.value else {
|
||||
assertionFailure()
|
||||
stateMachine.enter(Fail.self)
|
||||
return
|
||||
}
|
||||
|
||||
guard let lastFeedRecord = viewModel.fetchedResultsController.records.last else {
|
||||
stateMachine.enter(Idle.self)
|
||||
|
@ -92,7 +87,7 @@ extension HomeTimelineViewModel.LoadOldestState {
|
|||
do {
|
||||
let response = try await viewModel.context.apiService.homeTimeline(
|
||||
maxID: maxID,
|
||||
authenticationBox: activeMastodonAuthenticationBox
|
||||
authenticationBox: viewModel.authContext.mastodonAuthenticationBox
|
||||
)
|
||||
|
||||
let statuses = response.value
|
||||
|
|
|
@ -26,6 +26,7 @@ final class HomeTimelineViewModel: NSObject {
|
|||
|
||||
// input
|
||||
let context: AppContext
|
||||
let authContext: AuthContext
|
||||
let fetchedResultsController: FeedFetchedResultsController
|
||||
let homeTimelineNavigationBarTitleViewModel: HomeTimelineNavigationBarTitleViewModel
|
||||
let listBatchFetchViewModel = ListBatchFetchViewModel()
|
||||
|
@ -76,25 +77,17 @@ final class HomeTimelineViewModel: NSObject {
|
|||
|
||||
var cellFrameCache = NSCache<NSNumber, NSValue>()
|
||||
|
||||
init(context: AppContext) {
|
||||
init(context: AppContext, authContext: AuthContext) {
|
||||
self.context = context
|
||||
self.authContext = authContext
|
||||
self.fetchedResultsController = FeedFetchedResultsController(managedObjectContext: context.managedObjectContext)
|
||||
self.homeTimelineNavigationBarTitleViewModel = HomeTimelineNavigationBarTitleViewModel(context: context)
|
||||
super.init()
|
||||
|
||||
context.authenticationService.activeMastodonAuthenticationBox
|
||||
.sink { [weak self] authenticationBox in
|
||||
guard let self = self else { return }
|
||||
guard let authenticationBox = authenticationBox else {
|
||||
self.fetchedResultsController.predicate = Feed.predicate(kind: .none, acct: .none)
|
||||
return
|
||||
}
|
||||
self.fetchedResultsController.predicate = Feed.predicate(
|
||||
fetchedResultsController.predicate = Feed.predicate(
|
||||
kind: .home,
|
||||
acct: .mastodon(domain: authenticationBox.domain, userID: authenticationBox.userID)
|
||||
acct: .mastodon(domain: authContext.mastodonAuthenticationBox.domain, userID: authContext.mastodonAuthenticationBox.userID)
|
||||
)
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
|
||||
homeTimelineNeedRefresh
|
||||
.sink { [weak self] _ in
|
||||
|
@ -131,7 +124,6 @@ extension HomeTimelineViewModel {
|
|||
// load timeline gap
|
||||
func loadMore(item: StatusItem) async {
|
||||
guard case let .feedLoader(record) = item else { return }
|
||||
guard let authenticationBox = context.authenticationService.activeMastodonAuthenticationBox.value else { return }
|
||||
guard let diffableDataSource = diffableDataSource else { return }
|
||||
var snapshot = diffableDataSource.snapshot()
|
||||
|
||||
|
@ -169,7 +161,7 @@ extension HomeTimelineViewModel {
|
|||
let maxID = status.id
|
||||
_ = try await context.apiService.homeTimeline(
|
||||
maxID: maxID,
|
||||
authenticationBox: authenticationBox
|
||||
authenticationBox: authContext.mastodonAuthenticationBox
|
||||
)
|
||||
} catch {
|
||||
do {
|
||||
|
|
|
@ -147,6 +147,11 @@ extension NotificationTimelineViewController {
|
|||
|
||||
}
|
||||
|
||||
// MARK: - AuthContextProvider
|
||||
extension NotificationTimelineViewController: AuthContextProvider {
|
||||
var authContext: AuthContext { viewModel.authContext }
|
||||
}
|
||||
|
||||
// MARK: - UITableViewDelegate
|
||||
extension NotificationTimelineViewController: UITableViewDelegate, AutoGenerateTableViewDelegate {
|
||||
// sourcery:inline:NotificationTimelineViewController.AutoGenerateTableViewDelegate
|
||||
|
@ -297,9 +302,10 @@ extension NotificationTimelineViewController: TableViewControllerNavigateable {
|
|||
if let stauts = notification.status {
|
||||
let threadViewModel = ThreadViewModel(
|
||||
context: self.context,
|
||||
authContext: self.viewModel.authContext,
|
||||
optionalRoot: .root(context: .init(status: .init(objectID: stauts.objectID)))
|
||||
)
|
||||
self.coordinator.present(
|
||||
_ = self.coordinator.present(
|
||||
scene: .thread(viewModel: threadViewModel),
|
||||
from: self,
|
||||
transition: .show
|
||||
|
@ -307,9 +313,10 @@ extension NotificationTimelineViewController: TableViewControllerNavigateable {
|
|||
} else {
|
||||
let profileViewModel = ProfileViewModel(
|
||||
context: self.context,
|
||||
authContext: self.viewModel.authContext,
|
||||
optionalMastodonUser: notification.account
|
||||
)
|
||||
self.coordinator.present(
|
||||
_ = self.coordinator.present(
|
||||
scene: .profile(viewModel: profileViewModel),
|
||||
from: self,
|
||||
transition: .show
|
||||
|
|
|
@ -20,6 +20,7 @@ extension NotificationTimelineViewModel {
|
|||
tableView: tableView,
|
||||
context: context,
|
||||
configuration: NotificationSection.Configuration(
|
||||
authContext: authContext,
|
||||
notificationTableViewCellDelegate: notificationTableViewCellDelegate,
|
||||
filterContext: .notifications,
|
||||
activeFilters: context.statusFilterService.$activeFilters
|
||||
|
|
|
@ -63,11 +63,6 @@ extension NotificationTimelineViewModel.LoadOldestState {
|
|||
super.didEnter(from: previousState)
|
||||
|
||||
guard let viewModel = viewModel, let stateMachine = stateMachine else { return }
|
||||
guard let authenticationBox = viewModel.context.authenticationService.activeMastodonAuthenticationBox.value else {
|
||||
assertionFailure()
|
||||
stateMachine.enter(Fail.self)
|
||||
return
|
||||
}
|
||||
|
||||
guard let lastFeedRecord = viewModel.feedFetchedResultsController.records.last else {
|
||||
stateMachine.enter(Fail.self)
|
||||
|
@ -93,7 +88,7 @@ extension NotificationTimelineViewModel.LoadOldestState {
|
|||
let response = try await viewModel.context.apiService.notifications(
|
||||
maxID: maxID,
|
||||
scope: scope,
|
||||
authenticationBox: authenticationBox
|
||||
authenticationBox: viewModel.authContext.mastodonAuthenticationBox
|
||||
)
|
||||
|
||||
let notifications = response.value
|
||||
|
|
|
@ -21,6 +21,7 @@ final class NotificationTimelineViewModel {
|
|||
|
||||
// input
|
||||
let context: AppContext
|
||||
let authContext: AuthContext
|
||||
let scope: Scope
|
||||
let feedFetchedResultsController: FeedFetchedResultsController
|
||||
let listBatchFetchViewModel = ListBatchFetchViewModel()
|
||||
|
@ -47,28 +48,19 @@ final class NotificationTimelineViewModel {
|
|||
|
||||
init(
|
||||
context: AppContext,
|
||||
authContext: AuthContext,
|
||||
scope: Scope
|
||||
) {
|
||||
self.context = context
|
||||
self.authContext = authContext
|
||||
self.scope = scope
|
||||
self.feedFetchedResultsController = FeedFetchedResultsController(managedObjectContext: context.managedObjectContext)
|
||||
// end init
|
||||
|
||||
context.authenticationService.activeMastodonAuthenticationBox
|
||||
.sink { [weak self] authenticationBox in
|
||||
guard let self = self else { return }
|
||||
guard let authenticationBox = authenticationBox else {
|
||||
self.feedFetchedResultsController.predicate = Feed.nonePredicate()
|
||||
return
|
||||
}
|
||||
|
||||
let predicate = NotificationTimelineViewModel.feedPredicate(
|
||||
authenticationBox: authenticationBox,
|
||||
feedFetchedResultsController.predicate = NotificationTimelineViewModel.feedPredicate(
|
||||
authenticationBox: authContext.mastodonAuthenticationBox,
|
||||
scope: scope
|
||||
)
|
||||
self.feedFetchedResultsController.predicate = predicate
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
}
|
||||
|
||||
deinit {
|
||||
|
@ -122,8 +114,6 @@ extension NotificationTimelineViewModel {
|
|||
|
||||
// load lastest
|
||||
func loadLatest() async {
|
||||
guard let authenticationBox = context.authenticationService.activeMastodonAuthenticationBox.value else { return }
|
||||
|
||||
isLoadingLatest = true
|
||||
defer { isLoadingLatest = false }
|
||||
|
||||
|
@ -131,7 +121,7 @@ extension NotificationTimelineViewModel {
|
|||
_ = try await context.apiService.notifications(
|
||||
maxID: nil,
|
||||
scope: scope,
|
||||
authenticationBox: authenticationBox
|
||||
authenticationBox: authContext.mastodonAuthenticationBox
|
||||
)
|
||||
} catch {
|
||||
didLoadLatest.send()
|
||||
|
@ -142,7 +132,6 @@ extension NotificationTimelineViewModel {
|
|||
// load timeline gap
|
||||
func loadMore(item: NotificationItem) async {
|
||||
guard case let .feedLoader(record) = item else { return }
|
||||
guard let authenticationBox = context.authenticationService.activeMastodonAuthenticationBox.value else { return }
|
||||
|
||||
let managedObjectContext = context.managedObjectContext
|
||||
let key = "LoadMore@\(record.objectID)"
|
||||
|
@ -163,7 +152,7 @@ extension NotificationTimelineViewModel {
|
|||
_ = try await context.apiService.notifications(
|
||||
maxID: maxID,
|
||||
scope: scope,
|
||||
authenticationBox: authenticationBox
|
||||
authenticationBox: authContext.mastodonAuthenticationBox
|
||||
)
|
||||
} catch {
|
||||
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): fetch more failure: \(error.localizedDescription)")
|
||||
|
|
|
@ -24,7 +24,7 @@ final class NotificationViewController: TabmanViewController, NeedsDependency {
|
|||
var disposeBag = Set<AnyCancellable>()
|
||||
var observations = Set<NSKeyValueObservation>()
|
||||
|
||||
private(set) lazy var viewModel = NotificationViewModel(context: context)
|
||||
var viewModel: NotificationViewModel!
|
||||
|
||||
let pageSegmentedControl = UISegmentedControl()
|
||||
|
||||
|
@ -154,6 +154,7 @@ extension NotificationViewController {
|
|||
viewController.coordinator = coordinator
|
||||
viewController.viewModel = NotificationTimelineViewModel(
|
||||
context: context,
|
||||
authContext: viewModel.authContext,
|
||||
scope: scope
|
||||
)
|
||||
return viewController
|
||||
|
|
|
@ -19,6 +19,7 @@ final class NotificationViewModel {
|
|||
|
||||
// input
|
||||
let context: AppContext
|
||||
let authContext: AuthContext
|
||||
let viewDidLoad = PassthroughSubject<Void, Never>()
|
||||
|
||||
// output
|
||||
|
@ -27,8 +28,9 @@ final class NotificationViewModel {
|
|||
@Published var currentPageIndex = 0
|
||||
|
||||
|
||||
init(context: AppContext) {
|
||||
init(context: AppContext, authContext: AuthContext) {
|
||||
self.context = context
|
||||
self.authContext = authContext
|
||||
// end init
|
||||
}
|
||||
}
|
||||
|
|
|
@ -182,9 +182,13 @@ extension MastodonPickServerViewController {
|
|||
|
||||
authenticationViewModel
|
||||
.authenticated
|
||||
.flatMap { [weak self] (domain, user) -> AnyPublisher<Result<Bool, Error>, Never> in
|
||||
guard let self = self else { return Just(.success(false)).eraseToAnyPublisher() }
|
||||
return self.context.authenticationService.activeMastodonUser(domain: domain, userID: user.id)
|
||||
.asyncMap { domain, user -> Result<Bool, Error> in
|
||||
do {
|
||||
let result = try await self.context.authenticationService.activeMastodonUser(domain: domain, userID: user.id)
|
||||
return .success(result)
|
||||
} catch {
|
||||
return .failure(error)
|
||||
}
|
||||
}
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] result in
|
||||
|
|
|
@ -144,7 +144,7 @@ extension WelcomeViewController {
|
|||
signUpButton.addTarget(self, action: #selector(signUpButtonDidClicked(_:)), for: .touchUpInside)
|
||||
signInButton.addTarget(self, action: #selector(signInButtonDidClicked(_:)), for: .touchUpInside)
|
||||
|
||||
viewModel.needsShowDismissEntry
|
||||
viewModel.$needsShowDismissEntry
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] needsShowDismissEntry in
|
||||
guard let self = self else { return }
|
||||
|
|
|
@ -17,15 +17,14 @@ final class WelcomeViewModel {
|
|||
let context: AppContext
|
||||
|
||||
// output
|
||||
let needsShowDismissEntry = CurrentValueSubject<Bool, Never>(false)
|
||||
@Published var needsShowDismissEntry = false
|
||||
|
||||
init(context: AppContext) {
|
||||
self.context = context
|
||||
|
||||
context.authenticationService.mastodonAuthentications
|
||||
context.authenticationService.$mastodonAuthenticationBoxes
|
||||
.map { !$0.isEmpty }
|
||||
.assign(to: \.value, on: needsShowDismissEntry)
|
||||
.store(in: &disposeBag)
|
||||
.assign(to: &$needsShowDismissEntry)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -134,6 +134,11 @@ extension BookmarkViewController: UITableViewDelegate, AutoGenerateTableViewDele
|
|||
// MARK: - StatusTableViewCellDelegate
|
||||
extension BookmarkViewController: StatusTableViewCellDelegate { }
|
||||
|
||||
// MARK: - AuthContextProvider
|
||||
extension BookmarkViewController: AuthContextProvider {
|
||||
var authContext: AuthContext { viewModel.authContext }
|
||||
}
|
||||
|
||||
extension BookmarkViewController {
|
||||
override var keyCommands: [UIKeyCommand]? {
|
||||
return navigationKeyCommands + statusNavigationKeyCommands
|
||||
|
|
|
@ -17,6 +17,7 @@ extension BookmarkViewModel {
|
|||
tableView: tableView,
|
||||
context: context,
|
||||
configuration: StatusSection.Configuration(
|
||||
authContext: authContext,
|
||||
statusTableViewCellDelegate: statusTableViewCellDelegate,
|
||||
timelineMiddleLoaderTableViewCellDelegate: nil,
|
||||
filterContext: .none,
|
||||
|
|
|
@ -51,7 +51,7 @@ extension BookmarkViewModel.State {
|
|||
guard let viewModel = viewModel else { return false }
|
||||
switch stateClass {
|
||||
case is Reloading.Type:
|
||||
return viewModel.activeMastodonAuthenticationBox.value != nil
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
|
@ -134,20 +134,15 @@ extension BookmarkViewModel.State {
|
|||
super.didEnter(from: previousState)
|
||||
guard let viewModel = viewModel, let stateMachine = stateMachine else { return }
|
||||
|
||||
guard let authenticationBox = viewModel.activeMastodonAuthenticationBox.value else {
|
||||
stateMachine.enter(Fail.self)
|
||||
return
|
||||
}
|
||||
if previousState is Reloading {
|
||||
maxID = nil
|
||||
}
|
||||
|
||||
|
||||
Task {
|
||||
do {
|
||||
let response = try await viewModel.context.apiService.bookmarkedStatuses(
|
||||
maxID: maxID,
|
||||
authenticationBox: authenticationBox
|
||||
authenticationBox: viewModel.authContext.mastodonAuthenticationBox
|
||||
)
|
||||
|
||||
var hasNewStatusesAppend = false
|
||||
|
|
|
@ -18,7 +18,8 @@ final class BookmarkViewModel {
|
|||
|
||||
// input
|
||||
let context: AppContext
|
||||
let activeMastodonAuthenticationBox: CurrentValueSubject<MastodonAuthenticationBox?, Never>
|
||||
let authContext: AuthContext
|
||||
|
||||
let statusFetchedResultsController: StatusFetchedResultsController
|
||||
let listBatchFetchViewModel = ListBatchFetchViewModel()
|
||||
|
||||
|
@ -37,23 +38,14 @@ final class BookmarkViewModel {
|
|||
return stateMachine
|
||||
}()
|
||||
|
||||
init(context: AppContext) {
|
||||
init(context: AppContext, authContext: AuthContext) {
|
||||
self.context = context
|
||||
self.activeMastodonAuthenticationBox = CurrentValueSubject(context.authenticationService.activeMastodonAuthenticationBox.value)
|
||||
self.authContext = authContext
|
||||
self.statusFetchedResultsController = StatusFetchedResultsController(
|
||||
managedObjectContext: context.managedObjectContext,
|
||||
domain: nil,
|
||||
domain: authContext.mastodonAuthenticationBox.domain,
|
||||
additionalTweetPredicate: nil
|
||||
)
|
||||
|
||||
context.authenticationService.activeMastodonAuthenticationBox
|
||||
.assign(to: \.value, on: activeMastodonAuthenticationBox)
|
||||
.store(in: &disposeBag)
|
||||
|
||||
activeMastodonAuthenticationBox
|
||||
.map { $0?.domain }
|
||||
.assign(to: \.domain, on: statusFetchedResultsController)
|
||||
.store(in: &disposeBag)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -11,8 +11,8 @@ import MastodonCore
|
|||
|
||||
final class CachedProfileViewModel: ProfileViewModel {
|
||||
|
||||
init(context: AppContext, mastodonUser: MastodonUser) {
|
||||
super.init(context: context, optionalMastodonUser: mastodonUser)
|
||||
init(context: AppContext, authContext: AuthContext, mastodonUser: MastodonUser) {
|
||||
super.init(context: context, authContext: authContext, optionalMastodonUser: mastodonUser)
|
||||
|
||||
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): [Profile] user[\(mastodonUser.id)] profile: \(mastodonUser.acctWithDomain)")
|
||||
}
|
||||
|
|
|
@ -75,6 +75,13 @@ extension FamiliarFollowersViewController {
|
|||
|
||||
}
|
||||
|
||||
// MARK: - AuthContextProvider
|
||||
extension FamiliarFollowersViewController: AuthContextProvider {
|
||||
var authContext: AuthContext {
|
||||
viewModel.authContext
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - UITableViewDelegate
|
||||
extension FamiliarFollowersViewController: UITableViewDelegate, AutoGenerateTableViewDelegate {
|
||||
// sourcery:inline:FamiliarFollowersViewController.AutoGenerateTableViewDelegate
|
||||
|
|
|
@ -17,6 +17,7 @@ final class FamiliarFollowersViewModel {
|
|||
|
||||
// input
|
||||
let context: AppContext
|
||||
let authContext: AuthContext
|
||||
let userFetchedResultsController: UserFetchedResultsController
|
||||
|
||||
@Published var familiarFollowers: Mastodon.Entity.FamiliarFollowers?
|
||||
|
@ -24,20 +25,16 @@ final class FamiliarFollowersViewModel {
|
|||
// output
|
||||
var diffableDataSource: UITableViewDiffableDataSource<UserSection, UserItem>?
|
||||
|
||||
init(context: AppContext) {
|
||||
init(context: AppContext, authContext: AuthContext) {
|
||||
self.context = context
|
||||
self.authContext = authContext
|
||||
self.userFetchedResultsController = UserFetchedResultsController(
|
||||
managedObjectContext: context.managedObjectContext,
|
||||
domain: nil,
|
||||
domain: authContext.mastodonAuthenticationBox.domain,
|
||||
additionalPredicate: nil
|
||||
)
|
||||
// end init
|
||||
|
||||
context.authenticationService.activeMastodonAuthenticationBox
|
||||
.map { $0?.domain }
|
||||
.assign(to: \.domain, on: userFetchedResultsController)
|
||||
.store(in: &disposeBag)
|
||||
|
||||
$familiarFollowers
|
||||
.map { familiarFollowers -> [MastodonUser.ID] in
|
||||
guard let familiarFollowers = familiarFollowers else { return [] }
|
||||
|
|
|
@ -104,6 +104,11 @@ extension FavoriteViewController {
|
|||
|
||||
}
|
||||
|
||||
// MARK: - AuthContextProvider
|
||||
extension FavoriteViewController: AuthContextProvider {
|
||||
var authContext: AuthContext { viewModel.authContext }
|
||||
}
|
||||
|
||||
// MARK: - UITableViewDelegate
|
||||
extension FavoriteViewController: UITableViewDelegate, AutoGenerateTableViewDelegate {
|
||||
// sourcery:inline:FavoriteViewController.AutoGenerateTableViewDelegate
|
||||
|
|
|
@ -17,6 +17,7 @@ extension FavoriteViewModel {
|
|||
tableView: tableView,
|
||||
context: context,
|
||||
configuration: StatusSection.Configuration(
|
||||
authContext: authContext,
|
||||
statusTableViewCellDelegate: statusTableViewCellDelegate,
|
||||
timelineMiddleLoaderTableViewCellDelegate: nil,
|
||||
filterContext: .none,
|
||||
|
|
|
@ -51,7 +51,7 @@ extension FavoriteViewModel.State {
|
|||
guard let viewModel = viewModel else { return false }
|
||||
switch stateClass {
|
||||
case is Reloading.Type:
|
||||
return viewModel.activeMastodonAuthenticationBox.value != nil
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
|
@ -134,10 +134,6 @@ extension FavoriteViewModel.State {
|
|||
super.didEnter(from: previousState)
|
||||
guard let viewModel = viewModel, let stateMachine = stateMachine else { return }
|
||||
|
||||
guard let authenticationBox = viewModel.activeMastodonAuthenticationBox.value else {
|
||||
stateMachine.enter(Fail.self)
|
||||
return
|
||||
}
|
||||
if previousState is Reloading {
|
||||
maxID = nil
|
||||
}
|
||||
|
@ -147,7 +143,7 @@ extension FavoriteViewModel.State {
|
|||
do {
|
||||
let response = try await viewModel.context.apiService.favoritedStatuses(
|
||||
maxID: maxID,
|
||||
authenticationBox: authenticationBox
|
||||
authenticationBox: viewModel.authContext.mastodonAuthenticationBox
|
||||
)
|
||||
|
||||
var hasNewStatusesAppend = false
|
||||
|
|
|
@ -18,7 +18,7 @@ final class FavoriteViewModel {
|
|||
|
||||
// input
|
||||
let context: AppContext
|
||||
let activeMastodonAuthenticationBox: CurrentValueSubject<MastodonAuthenticationBox?, Never>
|
||||
let authContext: AuthContext
|
||||
let statusFetchedResultsController: StatusFetchedResultsController
|
||||
let listBatchFetchViewModel = ListBatchFetchViewModel()
|
||||
|
||||
|
@ -37,23 +37,14 @@ final class FavoriteViewModel {
|
|||
return stateMachine
|
||||
}()
|
||||
|
||||
init(context: AppContext) {
|
||||
init(context: AppContext, authContext: AuthContext) {
|
||||
self.context = context
|
||||
self.activeMastodonAuthenticationBox = CurrentValueSubject(context.authenticationService.activeMastodonAuthenticationBox.value)
|
||||
self.authContext = authContext
|
||||
self.statusFetchedResultsController = StatusFetchedResultsController(
|
||||
managedObjectContext: context.managedObjectContext,
|
||||
domain: nil,
|
||||
domain: authContext.mastodonAuthenticationBox.domain,
|
||||
additionalTweetPredicate: nil
|
||||
)
|
||||
|
||||
context.authenticationService.activeMastodonAuthenticationBox
|
||||
.assign(to: \.value, on: activeMastodonAuthenticationBox)
|
||||
.store(in: &disposeBag)
|
||||
|
||||
activeMastodonAuthenticationBox
|
||||
.map { $0?.domain }
|
||||
.assign(to: \.domain, on: statusFetchedResultsController)
|
||||
.store(in: &disposeBag)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -82,8 +82,8 @@ extension FollowerListViewController {
|
|||
|
||||
// trigger user timeline loading
|
||||
Publishers.CombineLatest(
|
||||
viewModel.domain.removeDuplicates().eraseToAnyPublisher(),
|
||||
viewModel.userID.removeDuplicates().eraseToAnyPublisher()
|
||||
viewModel.$domain.removeDuplicates(),
|
||||
viewModel.$userID.removeDuplicates()
|
||||
)
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] _ in
|
||||
|
@ -101,6 +101,12 @@ extension FollowerListViewController {
|
|||
|
||||
}
|
||||
|
||||
// MARK: - AuthContextProvider
|
||||
extension FollowerListViewController: AuthContextProvider {
|
||||
var authContext: AuthContext { viewModel.authContext }
|
||||
}
|
||||
|
||||
|
||||
// MARK: - UITableViewDelegate
|
||||
extension FollowerListViewController: UITableViewDelegate, AutoGenerateTableViewDelegate {
|
||||
// sourcery:inline:FollowerListViewController.AutoGenerateTableViewDelegate
|
||||
|
|
|
@ -50,10 +50,10 @@ extension FollowerListViewModel {
|
|||
case is State.Idle, is State.Loading, is State.Fail:
|
||||
snapshot.appendItems([.bottomLoader], toSection: .main)
|
||||
case is State.NoMore:
|
||||
guard let activeMastodonAuthenticationBox = self.context.authenticationService.activeMastodonAuthenticationBox.value,
|
||||
let userID = self.userID.value,
|
||||
userID != activeMastodonAuthenticationBox.userID
|
||||
guard let userID = self.userID,
|
||||
userID != self.authContext.mastodonAuthenticationBox.userID
|
||||
else { break }
|
||||
// display hint footer exclude self
|
||||
let text = L10n.Scene.Follower.footer
|
||||
snapshot.appendItems([.bottomHeader(text: text)], toSection: .main)
|
||||
default:
|
||||
|
|
|
@ -51,7 +51,7 @@ extension FollowerListViewModel.State {
|
|||
guard let viewModel = viewModel else { return false }
|
||||
switch stateClass {
|
||||
case is Reloading.Type:
|
||||
return viewModel.userID.value != nil
|
||||
return viewModel.userID != nil
|
||||
default:
|
||||
return false
|
||||
}
|
||||
|
@ -139,12 +139,7 @@ extension FollowerListViewModel.State {
|
|||
|
||||
guard let viewModel = viewModel, let stateMachine = stateMachine else { return }
|
||||
|
||||
guard let userID = viewModel.userID.value, !userID.isEmpty else {
|
||||
stateMachine.enter(Fail.self)
|
||||
return
|
||||
}
|
||||
|
||||
guard let authenticationBox = viewModel.context.authenticationService.activeMastodonAuthenticationBox.value else {
|
||||
guard let userID = viewModel.userID, !userID.isEmpty else {
|
||||
stateMachine.enter(Fail.self)
|
||||
return
|
||||
}
|
||||
|
@ -154,7 +149,7 @@ extension FollowerListViewModel.State {
|
|||
let response = try await viewModel.context.apiService.followers(
|
||||
userID: userID,
|
||||
maxID: maxID,
|
||||
authenticationBox: authenticationBox
|
||||
authenticationBox: viewModel.authContext.mastodonAuthenticationBox
|
||||
)
|
||||
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): fetch \(response.value.count) followers")
|
||||
|
||||
|
|
|
@ -20,11 +20,13 @@ final class FollowerListViewModel {
|
|||
|
||||
// input
|
||||
let context: AppContext
|
||||
let domain: CurrentValueSubject<String?, Never>
|
||||
let userID: CurrentValueSubject<String?, Never>
|
||||
let authContext: AuthContext
|
||||
let userFetchedResultsController: UserFetchedResultsController
|
||||
let listBatchFetchViewModel = ListBatchFetchViewModel()
|
||||
|
||||
@Published var domain: String?
|
||||
@Published var userID: String?
|
||||
|
||||
// output
|
||||
var diffableDataSource: UITableViewDiffableDataSource<UserSection, UserItem>?
|
||||
private(set) lazy var stateMachine: GKStateMachine = {
|
||||
|
@ -40,16 +42,21 @@ final class FollowerListViewModel {
|
|||
return stateMachine
|
||||
}()
|
||||
|
||||
init(context: AppContext, domain: String?, userID: String?) {
|
||||
init(
|
||||
context: AppContext,
|
||||
authContext: AuthContext,
|
||||
domain: String?,
|
||||
userID: String?
|
||||
) {
|
||||
self.context = context
|
||||
self.authContext = authContext
|
||||
self.userFetchedResultsController = UserFetchedResultsController(
|
||||
managedObjectContext: context.managedObjectContext,
|
||||
domain: domain,
|
||||
additionalPredicate: nil
|
||||
)
|
||||
self.domain = CurrentValueSubject(domain)
|
||||
self.userID = CurrentValueSubject(userID)
|
||||
// super.init()
|
||||
|
||||
self.domain = domain
|
||||
self.userID = userID
|
||||
// end init
|
||||
}
|
||||
}
|
||||
|
|
|
@ -82,8 +82,8 @@ extension FollowingListViewController {
|
|||
|
||||
// trigger user timeline loading
|
||||
Publishers.CombineLatest(
|
||||
viewModel.domain.removeDuplicates().eraseToAnyPublisher(),
|
||||
viewModel.userID.removeDuplicates().eraseToAnyPublisher()
|
||||
viewModel.$domain.removeDuplicates(),
|
||||
viewModel.$userID.removeDuplicates()
|
||||
)
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] _ in
|
||||
|
@ -101,6 +101,11 @@ extension FollowingListViewController {
|
|||
|
||||
}
|
||||
|
||||
// MARK: - AuthContextProvider
|
||||
extension FollowingListViewController: AuthContextProvider {
|
||||
var authContext: AuthContext { viewModel.authContext }
|
||||
}
|
||||
|
||||
// MARK: - UITableViewDelegate
|
||||
extension FollowingListViewController: UITableViewDelegate, AutoGenerateTableViewDelegate {
|
||||
// sourcery:inline:FollowingListViewController.AutoGenerateTableViewDelegate
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import UIKit
|
||||
import MastodonAsset
|
||||
import MastodonCore
|
||||
import MastodonLocalization
|
||||
|
||||
extension FollowingListViewModel {
|
||||
|
@ -50,10 +51,10 @@ extension FollowingListViewModel {
|
|||
case is State.Idle, is State.Loading, is State.Fail:
|
||||
snapshot.appendItems([.bottomLoader], toSection: .main)
|
||||
case is State.NoMore:
|
||||
guard let activeMastodonAuthenticationBox = self.context.authenticationService.activeMastodonAuthenticationBox.value,
|
||||
let userID = self.userID.value,
|
||||
userID != activeMastodonAuthenticationBox.userID
|
||||
guard let userID = self.userID,
|
||||
userID != self.authContext.mastodonAuthenticationBox.userID
|
||||
else { break }
|
||||
// display footer exclude self
|
||||
let text = L10n.Scene.Following.footer
|
||||
snapshot.appendItems([.bottomHeader(text: text)], toSection: .main)
|
||||
default:
|
||||
|
|
|
@ -50,7 +50,7 @@ extension FollowingListViewModel.State {
|
|||
guard let viewModel = viewModel else { return false }
|
||||
switch stateClass {
|
||||
case is Reloading.Type:
|
||||
return viewModel.userID.value != nil
|
||||
return viewModel.userID != nil
|
||||
default:
|
||||
return false
|
||||
}
|
||||
|
@ -138,12 +138,7 @@ extension FollowingListViewModel.State {
|
|||
|
||||
guard let viewModel = viewModel, let stateMachine = stateMachine else { return }
|
||||
|
||||
guard let userID = viewModel.userID.value, !userID.isEmpty else {
|
||||
stateMachine.enter(Fail.self)
|
||||
return
|
||||
}
|
||||
|
||||
guard let authenticationBox = viewModel.context.authenticationService.activeMastodonAuthenticationBox.value else {
|
||||
guard let userID = viewModel.userID, !userID.isEmpty else {
|
||||
stateMachine.enter(Fail.self)
|
||||
return
|
||||
}
|
||||
|
@ -153,7 +148,7 @@ extension FollowingListViewModel.State {
|
|||
let response = try await viewModel.context.apiService.following(
|
||||
userID: userID,
|
||||
maxID: maxID,
|
||||
authenticationBox: authenticationBox
|
||||
authenticationBox: viewModel.authContext.mastodonAuthenticationBox
|
||||
)
|
||||
|
||||
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): fetch \(response.value.count)")
|
||||
|
|
|
@ -19,11 +19,13 @@ final class FollowingListViewModel {
|
|||
|
||||
// input
|
||||
let context: AppContext
|
||||
let domain: CurrentValueSubject<String?, Never>
|
||||
let userID: CurrentValueSubject<String?, Never>
|
||||
let authContext: AuthContext
|
||||
let userFetchedResultsController: UserFetchedResultsController
|
||||
let listBatchFetchViewModel = ListBatchFetchViewModel()
|
||||
|
||||
@Published var domain: String?
|
||||
@Published var userID: String?
|
||||
|
||||
// output
|
||||
var diffableDataSource: UITableViewDiffableDataSource<UserSection, UserItem>?
|
||||
private(set) lazy var stateMachine: GKStateMachine = {
|
||||
|
@ -39,15 +41,21 @@ final class FollowingListViewModel {
|
|||
return stateMachine
|
||||
}()
|
||||
|
||||
init(context: AppContext, domain: String?, userID: String?) {
|
||||
init(
|
||||
context: AppContext,
|
||||
authContext: AuthContext,
|
||||
domain: String?,
|
||||
userID: String?
|
||||
) {
|
||||
self.context = context
|
||||
self.authContext = authContext
|
||||
self.userFetchedResultsController = UserFetchedResultsController(
|
||||
managedObjectContext: context.managedObjectContext,
|
||||
domain: domain,
|
||||
additionalPredicate: nil
|
||||
)
|
||||
self.domain = CurrentValueSubject(domain)
|
||||
self.userID = CurrentValueSubject(userID)
|
||||
self.domain = domain
|
||||
self.userID = userID
|
||||
// super.init()
|
||||
|
||||
}
|
||||
|
|
|
@ -332,10 +332,11 @@ extension ProfileHeaderViewController: ProfileHeaderViewDelegate {
|
|||
else { return }
|
||||
let followerListViewModel = FollowerListViewModel(
|
||||
context: context,
|
||||
authContext: viewModel.authContext,
|
||||
domain: domain,
|
||||
userID: userID
|
||||
)
|
||||
coordinator.present(
|
||||
_ = coordinator.present(
|
||||
scene: .follower(viewModel: followerListViewModel),
|
||||
from: self,
|
||||
transition: .show
|
||||
|
@ -346,10 +347,11 @@ extension ProfileHeaderViewController: ProfileHeaderViewDelegate {
|
|||
else { return }
|
||||
let followingListViewModel = FollowingListViewModel(
|
||||
context: context,
|
||||
authContext: viewModel.authContext,
|
||||
domain: domain,
|
||||
userID: userID
|
||||
)
|
||||
coordinator.present(
|
||||
_ = coordinator.present(
|
||||
scene: .following(viewModel: followingListViewModel),
|
||||
from: self,
|
||||
transition: .show
|
||||
|
|
|
@ -24,6 +24,8 @@ final class ProfileHeaderViewModel {
|
|||
|
||||
// input
|
||||
let context: AppContext
|
||||
let authContext: AuthContext
|
||||
|
||||
@Published var user: MastodonUser?
|
||||
@Published var relationshipActionOptionSet: RelationshipActionOptionSet = .none
|
||||
|
||||
|
@ -41,8 +43,9 @@ final class ProfileHeaderViewModel {
|
|||
@Published var isTitleViewDisplaying = false
|
||||
@Published var isTitleViewContentOffsetSet = false
|
||||
|
||||
init(context: AppContext) {
|
||||
init(context: AppContext, authContext: AuthContext) {
|
||||
self.context = context
|
||||
self.authContext = authContext
|
||||
|
||||
$accountForEdit
|
||||
.receive(on: DispatchQueue.main)
|
||||
|
|
|
@ -15,10 +15,12 @@ import MastodonSDK
|
|||
|
||||
final class MeProfileViewModel: ProfileViewModel {
|
||||
|
||||
init(context: AppContext) {
|
||||
init(context: AppContext, authContext: AuthContext) {
|
||||
let user = authContext.mastodonAuthenticationBox.authenticationRecord.object(in: context.managedObjectContext)?.user
|
||||
super.init(
|
||||
context: context,
|
||||
optionalMastodonUser: context.authenticationService.activeMastodonAuthentication.value?.user
|
||||
authContext: authContext,
|
||||
optionalMastodonUser: user
|
||||
)
|
||||
|
||||
$me
|
||||
|
|
|
@ -111,7 +111,7 @@ final class ProfileViewController: UIViewController, NeedsDependency, MediaPrevi
|
|||
let viewController = ProfileHeaderViewController()
|
||||
viewController.context = context
|
||||
viewController.coordinator = coordinator
|
||||
viewController.viewModel = ProfileHeaderViewModel(context: context)
|
||||
viewController.viewModel = ProfileHeaderViewModel(context: context, authContext: viewModel.authContext)
|
||||
return viewController
|
||||
}()
|
||||
|
||||
|
@ -460,14 +460,14 @@ extension ProfileViewController {
|
|||
switch meta {
|
||||
case .url(_, _, let url, _):
|
||||
guard let url = URL(string: url) else { return }
|
||||
coordinator.present(scene: .safari(url: url), from: nil, transition: .safariPresent(animated: true, completion: nil))
|
||||
_ = coordinator.present(scene: .safari(url: url), from: nil, transition: .safariPresent(animated: true, completion: nil))
|
||||
case .mention(_, _, let userInfo):
|
||||
guard let href = userInfo?["href"] as? String,
|
||||
let url = URL(string: href) else { return }
|
||||
coordinator.present(scene: .safari(url: url), from: nil, transition: .safariPresent(animated: true, completion: nil))
|
||||
_ = coordinator.present(scene: .safari(url: url), from: nil, transition: .safariPresent(animated: true, completion: nil))
|
||||
case .hashtag(_, let hashtag, _):
|
||||
let hashtagTimelineViewModel = HashtagTimelineViewModel(context: context, hashtag: hashtag)
|
||||
coordinator.present(scene: .hashtagTimeline(viewModel: hashtagTimelineViewModel), from: nil, transition: .show)
|
||||
let hashtagTimelineViewModel = HashtagTimelineViewModel(context: context, authContext: viewModel.authContext, hashtag: hashtag)
|
||||
_ = coordinator.present(scene: .hashtagTimeline(viewModel: hashtagTimelineViewModel), from: nil, transition: .show)
|
||||
case .email, .emoji:
|
||||
break
|
||||
}
|
||||
|
@ -485,7 +485,7 @@ extension ProfileViewController {
|
|||
@objc private func settingBarButtonItemPressed(_ sender: UIBarButtonItem) {
|
||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
|
||||
guard let setting = context.settingService.currentSetting.value else { return }
|
||||
let settingsViewModel = SettingsViewModel(context: context, setting: setting)
|
||||
let settingsViewModel = SettingsViewModel(context: context, authContext: viewModel.authContext, setting: setting)
|
||||
coordinator.present(scene: .settings(viewModel: settingsViewModel), from: self, transition: .modal(animated: true, completion: nil))
|
||||
}
|
||||
|
||||
|
@ -513,24 +513,23 @@ extension ProfileViewController {
|
|||
|
||||
@objc private func favoriteBarButtonItemPressed(_ sender: UIBarButtonItem) {
|
||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
|
||||
let favoriteViewModel = FavoriteViewModel(context: context)
|
||||
let favoriteViewModel = FavoriteViewModel(context: context, authContext: viewModel.authContext)
|
||||
coordinator.present(scene: .favorite(viewModel: favoriteViewModel), from: self, transition: .show)
|
||||
}
|
||||
|
||||
@objc private func bookmarkBarButtonItemPressed(_ sender: UIBarButtonItem) {
|
||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
|
||||
let bookmarkViewModel = BookmarkViewModel(context: context)
|
||||
let bookmarkViewModel = BookmarkViewModel(context: context, authContext: viewModel.authContext)
|
||||
coordinator.present(scene: .bookmark(viewModel: bookmarkViewModel), from: self, transition: .show)
|
||||
}
|
||||
|
||||
@objc private func replyBarButtonItemPressed(_ sender: UIBarButtonItem) {
|
||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
|
||||
guard let authenticationBox = context.authenticationService.activeMastodonAuthenticationBox.value else { return }
|
||||
guard let mastodonUser = viewModel.user else { return }
|
||||
let composeViewModel = ComposeViewModel(
|
||||
context: context,
|
||||
composeKind: .mention(user: .init(objectID: mastodonUser.objectID)),
|
||||
authenticationBox: authenticationBox
|
||||
authContext: viewModel.authContext
|
||||
)
|
||||
coordinator.present(scene: .compose(viewModel: composeViewModel), from: self, transition: .modal(animated: true, completion: nil))
|
||||
}
|
||||
|
@ -671,6 +670,11 @@ extension ProfileViewController: TabBarPagerDataSource {
|
|||
//
|
||||
//}
|
||||
|
||||
// MARK: - AuthContextProvider
|
||||
extension ProfileViewController: AuthContextProvider {
|
||||
var authContext: AuthContext { viewModel.authContext }
|
||||
}
|
||||
|
||||
// MARK: - ProfileHeaderViewControllerDelegate
|
||||
extension ProfileViewController: ProfileHeaderViewControllerDelegate {
|
||||
func profileHeaderViewController(
|
||||
|
@ -760,16 +764,13 @@ extension ProfileViewController: ProfileHeaderViewControllerDelegate {
|
|||
case .follow, .request, .pending, .following:
|
||||
guard let user = viewModel.user else { return }
|
||||
let reocrd = ManagedObjectRecord<MastodonUser>(objectID: user.objectID)
|
||||
guard let authenticationBox = context.authenticationService.activeMastodonAuthenticationBox.value else { return }
|
||||
Task {
|
||||
try await DataSourceFacade.responseToUserFollowAction(
|
||||
dependency: self,
|
||||
user: reocrd,
|
||||
authenticationBox: authenticationBox
|
||||
user: reocrd
|
||||
)
|
||||
}
|
||||
case .muting:
|
||||
guard let authenticationBox = self.context.authenticationService.activeMastodonAuthenticationBox.value else { return }
|
||||
guard let user = viewModel.user else { return }
|
||||
let name = user.displayNameWithFallback
|
||||
|
||||
|
@ -784,8 +785,7 @@ extension ProfileViewController: ProfileHeaderViewControllerDelegate {
|
|||
Task {
|
||||
try await DataSourceFacade.responseToUserMuteAction(
|
||||
dependency: self,
|
||||
user: record,
|
||||
authenticationBox: authenticationBox
|
||||
user: record
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -794,7 +794,6 @@ extension ProfileViewController: ProfileHeaderViewControllerDelegate {
|
|||
alertController.addAction(cancelAction)
|
||||
present(alertController, animated: true, completion: nil)
|
||||
case .blocking:
|
||||
guard let authenticationBox = self.context.authenticationService.activeMastodonAuthenticationBox.value else { return }
|
||||
guard let user = viewModel.user else { return }
|
||||
let name = user.displayNameWithFallback
|
||||
|
||||
|
@ -809,8 +808,7 @@ extension ProfileViewController: ProfileHeaderViewControllerDelegate {
|
|||
Task {
|
||||
try await DataSourceFacade.responseToUserBlockAction(
|
||||
dependency: self,
|
||||
user: record,
|
||||
authenticationBox: authenticationBox
|
||||
user: record
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -852,7 +850,6 @@ extension ProfileViewController: ProfileAboutViewControllerDelegate {
|
|||
// MARK: - MastodonMenuDelegate
|
||||
extension ProfileViewController: MastodonMenuDelegate {
|
||||
func menuAction(_ action: MastodonMenu.Action) {
|
||||
guard let authenticationBox = context.authenticationService.activeMastodonAuthenticationBox.value else { return }
|
||||
guard let user = viewModel.user else { return }
|
||||
|
||||
let userRecord: ManagedObjectRecord<MastodonUser> = .init(objectID: user.objectID)
|
||||
|
@ -866,8 +863,7 @@ extension ProfileViewController: MastodonMenuDelegate {
|
|||
status: nil,
|
||||
button: nil,
|
||||
barButtonItem: self.moreMenuBarButtonItem
|
||||
),
|
||||
authenticationBox: authenticationBox
|
||||
)
|
||||
)
|
||||
} // end Task
|
||||
}
|
||||
|
|
|
@ -35,6 +35,7 @@ class ProfileViewModel: NSObject {
|
|||
|
||||
// input
|
||||
let context: AppContext
|
||||
let authContext: AuthContext
|
||||
@Published var me: MastodonUser?
|
||||
@Published var user: MastodonUser?
|
||||
|
||||
|
@ -58,21 +59,25 @@ class ProfileViewModel: NSObject {
|
|||
// @Published var protected: Bool? = nil
|
||||
// let needsPagePinToTop = CurrentValueSubject<Bool, Never>(false)
|
||||
|
||||
init(context: AppContext, optionalMastodonUser mastodonUser: MastodonUser?) {
|
||||
init(context: AppContext, authContext: AuthContext, optionalMastodonUser mastodonUser: MastodonUser?) {
|
||||
self.context = context
|
||||
self.authContext = authContext
|
||||
self.user = mastodonUser
|
||||
self.postsUserTimelineViewModel = UserTimelineViewModel(
|
||||
context: context,
|
||||
authContext: authContext,
|
||||
title: L10n.Scene.Profile.SegmentedControl.posts,
|
||||
queryFilter: .init(excludeReplies: true)
|
||||
)
|
||||
self.repliesUserTimelineViewModel = UserTimelineViewModel(
|
||||
context: context,
|
||||
authContext: authContext,
|
||||
title: L10n.Scene.Profile.SegmentedControl.postsAndReplies,
|
||||
queryFilter: .init(excludeReplies: false)
|
||||
)
|
||||
self.mediaUserTimelineViewModel = UserTimelineViewModel(
|
||||
context: context,
|
||||
authContext: authContext,
|
||||
title: L10n.Scene.Profile.SegmentedControl.media,
|
||||
queryFilter: .init(onlyMedia: true)
|
||||
)
|
||||
|
@ -80,13 +85,7 @@ class ProfileViewModel: NSObject {
|
|||
super.init()
|
||||
|
||||
// bind me
|
||||
context.authenticationService.activeMastodonAuthenticationBox
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] authenticationBox in
|
||||
guard let self = self else { return }
|
||||
self.me = authenticationBox?.authenticationRecord.object(in: context.managedObjectContext)?.user
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
self.me = authContext.mastodonAuthenticationBox.authenticationRecord.object(in: context.managedObjectContext)?.user
|
||||
$me
|
||||
.assign(to: \.me, on: relationshipViewModel)
|
||||
.store(in: &disposeBag)
|
||||
|
@ -132,21 +131,18 @@ class ProfileViewModel: NSObject {
|
|||
let pendingRetryPublisher = CurrentValueSubject<TimeInterval, Never>(1)
|
||||
|
||||
// observe friendship
|
||||
Publishers.CombineLatest3(
|
||||
Publishers.CombineLatest(
|
||||
userRecord,
|
||||
context.authenticationService.activeMastodonAuthenticationBox,
|
||||
pendingRetryPublisher
|
||||
)
|
||||
.sink { [weak self] userRecord, authenticationBox, _ in
|
||||
.sink { [weak self] userRecord, _ in
|
||||
guard let self = self else { return }
|
||||
guard let userRecord = userRecord,
|
||||
let authenticationBox = authenticationBox
|
||||
else { return }
|
||||
guard let userRecord = userRecord else { return }
|
||||
Task {
|
||||
do {
|
||||
let response = try await self.updateRelationship(
|
||||
record: userRecord,
|
||||
authenticationBox: authenticationBox
|
||||
authenticationBox: self.authContext.mastodonAuthenticationBox
|
||||
)
|
||||
// there are seconds delay after request follow before requested -> following. Query again when needs
|
||||
guard let relationship = response.value.first else { return }
|
||||
|
@ -216,10 +212,7 @@ extension ProfileViewModel {
|
|||
headerProfileInfo: ProfileHeaderViewModel.ProfileInfo,
|
||||
aboutProfileInfo: ProfileAboutViewModel.ProfileInfo
|
||||
) async throws -> Mastodon.Response.Content<Mastodon.Entity.Account> {
|
||||
guard let authenticationBox = context.authenticationService.activeMastodonAuthenticationBox.value else {
|
||||
throw APIService.APIError.implicit(.badRequest)
|
||||
}
|
||||
|
||||
let authenticationBox = authContext.mastodonAuthenticationBox
|
||||
let domain = authenticationBox.domain
|
||||
let authorization = authenticationBox.userAuthorization
|
||||
|
||||
|
|
|
@ -14,14 +14,11 @@ import MastodonCore
|
|||
|
||||
final class RemoteProfileViewModel: ProfileViewModel {
|
||||
|
||||
init(context: AppContext, userID: Mastodon.Entity.Account.ID) {
|
||||
super.init(context: context, optionalMastodonUser: nil)
|
||||
init(context: AppContext, authContext: AuthContext, userID: Mastodon.Entity.Account.ID) {
|
||||
super.init(context: context, authContext: authContext, optionalMastodonUser: nil)
|
||||
|
||||
guard let activeMastodonAuthenticationBox = context.authenticationService.activeMastodonAuthenticationBox.value else {
|
||||
return
|
||||
}
|
||||
let domain = activeMastodonAuthenticationBox.domain
|
||||
let authorization = activeMastodonAuthenticationBox.userAuthorization
|
||||
let domain = authContext.mastodonAuthenticationBox.domain
|
||||
let authorization = authContext.mastodonAuthenticationBox.userAuthorization
|
||||
Just(userID)
|
||||
.asyncMap { userID in
|
||||
try await context.apiService.accountInfo(
|
||||
|
@ -54,23 +51,19 @@ final class RemoteProfileViewModel: ProfileViewModel {
|
|||
.store(in: &disposeBag)
|
||||
}
|
||||
|
||||
init(context: AppContext, notificationID: Mastodon.Entity.Notification.ID) {
|
||||
super.init(context: context, optionalMastodonUser: nil)
|
||||
|
||||
guard let authenticationBox = context.authenticationService.activeMastodonAuthenticationBox.value else {
|
||||
return
|
||||
}
|
||||
init(context: AppContext, authContext: AuthContext, notificationID: Mastodon.Entity.Notification.ID) {
|
||||
super.init(context: context, authContext: authContext, optionalMastodonUser: nil)
|
||||
|
||||
Task { @MainActor in
|
||||
let response = try await context.apiService.notification(
|
||||
notificationID: notificationID,
|
||||
authenticationBox: authenticationBox
|
||||
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: authenticationBox.domain, id: userID)
|
||||
request.predicate = MastodonUser.predicate(domain: authContext.mastodonAuthenticationBox.domain, id: userID)
|
||||
request.fetchLimit = 1
|
||||
return context.managedObjectContext.safeFetch(request).first
|
||||
}
|
||||
|
@ -79,14 +72,14 @@ final class RemoteProfileViewModel: ProfileViewModel {
|
|||
self.user = user
|
||||
} else {
|
||||
_ = try await context.apiService.accountInfo(
|
||||
domain: authenticationBox.domain,
|
||||
domain: authContext.mastodonAuthenticationBox.domain,
|
||||
userID: userID,
|
||||
authorization: authenticationBox.userAuthorization
|
||||
authorization: authContext.mastodonAuthenticationBox.userAuthorization
|
||||
)
|
||||
|
||||
let _user: MastodonUser? = try await context.managedObjectContext.perform {
|
||||
let request = MastodonUser.sortedFetchRequest
|
||||
request.predicate = MastodonUser.predicate(domain: authenticationBox.domain, id: userID)
|
||||
request.predicate = MastodonUser.predicate(domain: authContext.mastodonAuthenticationBox.domain, id: userID)
|
||||
request.fetchLimit = 1
|
||||
return context.managedObjectContext.safeFetch(request).first
|
||||
}
|
||||
|
|
|
@ -103,6 +103,11 @@ extension UserTimelineViewController: CellFrameCacheContainer {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: - AuthContextProvider
|
||||
extension UserTimelineViewController: AuthContextProvider {
|
||||
var authContext: AuthContext { viewModel.authContext }
|
||||
}
|
||||
|
||||
// MARK: - UITableViewDelegate
|
||||
extension UserTimelineViewController: UITableViewDelegate, AutoGenerateTableViewDelegate {
|
||||
// sourcery:inline:UserTimelineViewController.AutoGenerateTableViewDelegate
|
||||
|
|
|
@ -18,6 +18,7 @@ extension UserTimelineViewModel {
|
|||
tableView: tableView,
|
||||
context: context,
|
||||
configuration: StatusSection.Configuration(
|
||||
authContext: authContext,
|
||||
statusTableViewCellDelegate: statusTableViewCellDelegate,
|
||||
timelineMiddleLoaderTableViewCellDelegate: nil,
|
||||
filterContext: .none,
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue