From bb5c999bea56b987b6e843eba3b9e8288ad9bd2b Mon Sep 17 00:00:00 2001 From: CMK Date: Sun, 9 Oct 2022 20:07:57 +0800 Subject: [PATCH] chore: [WIP] inject AuthContext into ViewModel --- .github/scripts/build.sh | 1 - Mastodon.xcodeproj/project.pbxproj | 8 +- .../xcschemes/xcschememanagement.plist | 4 +- .../xcshareddata/swiftpm/Package.resolved | 474 +++++++++--------- Mastodon/Coordinator/SceneCoordinator.swift | 183 ++++--- .../ComposeStatusAttachmentSection.swift | 1 - .../Discovery/DiscoverySection.swift | 9 +- .../Notification/NotificationSection.swift | 10 +- .../RecommendAccountSection.swift | 6 +- Mastodon/Diffiable/Report/ReportSection.swift | 7 +- .../Search/SearchResultSection.swift | 9 +- Mastodon/Diffiable/Status/StatusSection.swift | 24 +- .../Provider/DataSourceFacade+Block.swift | 7 +- .../Provider/DataSourceFacade+Bookmark.swift | 7 +- .../Provider/DataSourceFacade+Favorite.swift | 7 +- .../Provider/DataSourceFacade+Follow.swift | 14 +- .../Provider/DataSourceFacade+Hashtag.swift | 9 +- .../Provider/DataSourceFacade+Meta.swift | 12 +- .../Provider/DataSourceFacade+Mute.swift | 7 +- .../Provider/DataSourceFacade+Profile.swift | 19 +- .../Provider/DataSourceFacade+Reblog.swift | 7 +- .../DataSourceFacade+SearchHistory.swift | 16 +- .../Provider/DataSourceFacade+Status.swift | 43 +- .../Provider/DataSourceFacade+Thread.swift | 8 +- ...er+NotificationTableViewCellDelegate.swift | 39 +- ...Provider+StatusTableViewCellDelegate.swift | 31 +- ...tatusTableViewControllerNavigateable.swift | 20 +- ...ider+TableViewControllerNavigateable.swift | 3 +- ...taSourceProvider+UITableViewDelegate.swift | 2 +- .../Scene/Account/AccountListViewModel.swift | 116 +++-- .../Scene/Account/AccountViewController.swift | 23 +- .../AutoCompleteViewModel+State.swift | 7 +- .../AutoComplete/AutoCompleteViewModel.swift | 4 +- .../Scene/Compose/ComposeViewController.swift | 2 +- Mastodon/Scene/Compose/ComposeViewModel.swift | 22 +- .../DiscoveryCommunityViewController.swift | 5 + ...DiscoveryCommunityViewModel+Diffable.swift | 1 + .../DiscoveryCommunityViewModel+State.swift | 7 +- .../DiscoveryCommunityViewModel.swift | 12 +- .../Discovery/DiscoveryViewController.swift | 7 +- .../Scene/Discovery/DiscoveryViewModel.swift | 17 +- .../DiscoveryForYouViewController.swift | 16 +- .../DiscoveryForYouViewModel+Diffable.swift | 1 + .../ForYou/DiscoveryForYouViewModel.swift | 25 +- .../DiscoveryHashtagsViewController.swift | 4 +- .../DiscoveryHashtagsViewModel+Diffable.swift | 2 +- .../Hashtags/DiscoveryHashtagsViewModel.swift | 49 +- .../DiscoveryNewsViewModel+Diffable.swift | 2 +- .../News/DiscoveryNewsViewModel+State.swift | 7 +- .../News/DiscoveryNewsViewModel.swift | 8 +- .../Posts/DiscoveryPostsViewController.swift | 5 + .../DiscoveryPostsViewModel+Diffable.swift | 1 + .../Posts/DiscoveryPostsViewModel+State.swift | 7 +- .../Posts/DiscoveryPostsViewModel.swift | 15 +- .../HashtagTimelineViewController.swift | 8 +- .../HashtagTimelineViewModel+Diffable.swift | 1 + .../HashtagTimelineViewModel+State.swift | 10 +- .../HashtagTimelineViewModel.swift | 11 +- ...meTimelineViewController+DebugAction.swift | 34 +- .../HomeTimelineViewController.swift | 19 +- .../HomeTimelineViewModel+Diffable.swift | 1 + ...omeTimelineViewModel+LoadLatestState.swift | 7 +- ...omeTimelineViewModel+LoadOldestState.swift | 7 +- .../HomeTimeline/HomeTimelineViewModel.swift | 24 +- .../NotificationTimelineViewController.swift | 11 +- ...tificationTimelineViewModel+Diffable.swift | 1 + ...ionTimelineViewModel+LoadOldestState.swift | 7 +- .../NotificationTimelineViewModel.swift | 29 +- .../NotificationViewController.swift | 3 +- .../Notification/NotificationViewModel.swift | 4 +- .../MastodonPickServerViewController.swift | 10 +- .../Welcome/WelcomeViewController.swift | 2 +- .../Onboarding/Welcome/WelcomeViewModel.swift | 7 +- .../Bookmark/BookmarkViewController.swift | 5 + .../Bookmark/BookmarkViewModel+Diffable.swift | 1 + .../Bookmark/BookmarkViewModel+State.swift | 9 +- .../Profile/Bookmark/BookmarkViewModel.swift | 18 +- .../Profile/CachedProfileViewModel.swift | 4 +- .../FamiliarFollowersViewController.swift | 7 + .../FamiliarFollowersViewModel.swift | 11 +- .../Favorite/FavoriteViewController.swift | 5 + .../Favorite/FavoriteViewModel+Diffable.swift | 1 + .../Favorite/FavoriteViewModel+State.swift | 8 +- .../Profile/Favorite/FavoriteViewModel.swift | 17 +- .../Follower/FollowerListViewController.swift | 22 +- .../FollowerListViewModel+Diffable.swift | 6 +- .../FollowerListViewModel+State.swift | 11 +- .../Follower/FollowerListViewModel.swift | 21 +- .../FollowingListViewController.swift | 9 +- .../FollowingListViewModel+Diffable.swift | 11 +- .../FollowingListViewModel+State.swift | 11 +- .../Following/FollowingListViewModel.swift | 18 +- .../Header/ProfileHeaderViewController.swift | 6 +- .../Header/ProfileHeaderViewModel.swift | 5 +- .../Scene/Profile/MeProfileViewModel.swift | 6 +- .../Scene/Profile/ProfileViewController.swift | 40 +- Mastodon/Scene/Profile/ProfileViewModel.swift | 31 +- .../Profile/RemoteProfileViewModel.swift | 29 +- .../Timeline/UserTimelineViewController.swift | 5 + .../UserTimelineViewModel+Diffable.swift | 1 + .../UserTimelineViewModel+State.swift | 6 +- .../Timeline/UserTimelineViewModel.swift | 11 +- .../FavoritedByViewController.swift | 5 + .../RebloggedByViewController.swift | 5 + .../UserLIst/UserListViewModel+State.swift | 8 +- .../Profile/UserLIst/UserListViewModel.swift | 10 +- .../Report/Report/ReportViewController.swift | 14 +- .../Scene/Report/Report/ReportViewModel.swift | 20 +- .../ReportResult/ReportResultView.swift | 128 ++--- .../ReportResultViewController.swift | 23 +- .../ReportResult/ReportResultViewModel.swift | 5 +- .../ReportStatusViewModel+Diffable.swift | 2 +- .../ReportStatusViewModel+State.swift | 6 +- .../ReportStatus/ReportStatusViewModel.swift | 12 +- ...eportSupplementaryViewModel+Diffable.swift | 2 +- .../ReportSupplementaryViewModel.swift | 5 +- .../Root/ContentSplitViewController.swift | 10 +- .../Root/MainTab/MainTabBarController.swift | 97 ++-- .../Scene/Root/RootSplitViewController.swift | 4 + .../Root/Sidebar/SidebarViewController.swift | 11 +- .../Scene/Root/Sidebar/SidebarViewModel.swift | 38 +- .../Search/Search/SearchViewController.swift | 15 +- .../Scene/Search/Search/SearchViewModel.swift | 4 +- .../SearchDetailViewController.swift | 4 +- .../SearchDetail/SearchDetailViewModel.swift | 5 +- .../SearchHistoryViewController.swift | 5 + .../SearchHistoryViewModel.swift | 14 +- .../SearchResultViewController.swift | 7 +- .../SearchResultViewModel+Diffable.swift | 1 + .../SearchResultViewModel+State.swift | 7 +- .../SearchResult/SearchResultViewModel.swift | 18 +- .../Settings/SettingsViewController.swift | 18 +- .../Scene/Settings/SettingsViewModel.swift | 18 +- .../NotificationView+Configuration.swift | 57 +-- .../PollOptionView+Configuration.swift | 8 +- .../Content/StatusView+Configuration.swift | 2 +- .../SuggestionAccountViewController.swift | 12 +- .../SuggestionAccountViewModel+Diffable.swift | 1 + .../SuggestionAccountViewModel.swift | 25 +- .../Scene/Thread/CachedThreadViewModel.swift | 3 +- .../Scene/Thread/RemoteThreadViewModel.swift | 20 +- .../Scene/Thread/ThreadViewController.swift | 12 +- .../Thread/ThreadViewModel+Diffable.swift | 1 + .../ThreadViewModel+LoadThreadState.swift | 8 +- Mastodon/Scene/Thread/ThreadViewModel.swift | 3 + Mastodon/Supporting Files/SceneDelegate.swift | 4 +- .../Utility/ManagedObjectRecord.swift | 6 + .../Preference/Preference+Notification.swift | 2 +- .../Sources/MastodonCore/AppSecret.swift | 2 +- .../Service/AuthenticationService.swift | 88 +--- .../Service/BlockDomainService.swift | 31 +- .../Service/InstanceService.swift | 4 +- .../Notification/NotificationService.swift | 12 +- .../MastodonCore/Service/SettingService.swift | 18 +- .../Service/StatusFilterService.swift | 7 +- .../Content/NotificationView+ViewModel.swift | 10 +- .../View/Content/NotificationView.swift | 2 + .../Content/PollOptionView+ViewModel.swift | 2 +- .../View/Content/StatusView+ViewModel.swift | 18 +- Podfile.lock | 2 +- 160 files changed, 1329 insertions(+), 1435 deletions(-) diff --git a/.github/scripts/build.sh b/.github/scripts/build.sh index f5894901a..3c570b1af 100755 --- a/.github/scripts/build.sh +++ b/.github/scripts/build.sh @@ -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 diff --git a/Mastodon.xcodeproj/project.pbxproj b/Mastodon.xcodeproj/project.pbxproj index d8a01b818..30f9c0c2d 100644 --- a/Mastodon.xcodeproj/project.pbxproj +++ b/Mastodon.xcodeproj/project.pbxproj @@ -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 = ""; diff --git a/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist b/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist index 498c362d6..21906eb03 100644 --- a/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist @@ -112,12 +112,12 @@ NotificationService.xcscheme_^#shared#^_ orderHint - 7 + 25 ShareActionExtension.xcscheme_^#shared#^_ orderHint - 6 + 24 SuppressBuildableAutocreation diff --git a/Mastodon.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Mastodon.xcworkspace/xcshareddata/swiftpm/Package.resolved index ebcacb501..4ee0ebeb7 100644 --- a/Mastodon.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Mastodon.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,241 +1,239 @@ { - "object": { - "pins": [ - { - "package": "Alamofire", - "repositoryURL": "https://github.com/Alamofire/Alamofire.git", - "state": { - "branch": null, - "revision": "354dda32d89fc8cd4f5c46487f64957d355f53d8", - "version": "5.6.1" - } - }, - { - "package": "AlamofireImage", - "repositoryURL": "https://github.com/Alamofire/AlamofireImage.git", - "state": { - "branch": null, - "revision": "98cbb00ce0ec5fc8e52a5b50a6bfc08d3e5aee10", - "version": "4.2.0" - } - }, - { - "package": "CommonOSLog", - "repositoryURL": "https://github.com/MainasuK/CommonOSLog", - "state": { - "branch": null, - "revision": "c121624a30698e9886efe38aebb36ff51c01b6c2", - "version": "0.1.1" - } - }, - { - "package": "FaviconFinder", - "repositoryURL": "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", - "state": { - "branch": null, - "revision": "e7f9fd4681ae41bf6f3056db08af4f401d61da52", - "version": "1.0.16" - } - }, - { - "package": "FPSIndicator", - "repositoryURL": "https://github.com/MainasuK/FPSIndicator.git", - "state": { - "branch": null, - "revision": "e4a5067ccd5293b024c767f09e51056afd4a4796", - "version": "1.1.0" - } - }, - { - "package": "Fuzi", - "repositoryURL": "https://github.com/cezheng/Fuzi.git", - "state": { - "branch": null, - "revision": "f08c8323da21e985f3772610753bcfc652c2103f", - "version": "3.1.3" - } - }, - { - "package": "KeychainAccess", - "repositoryURL": "https://github.com/kishikawakatsumi/KeychainAccess.git", - "state": { - "branch": null, - "revision": "84e546727d66f1adc5439debad16270d0fdd04e7", - "version": "4.2.2" - } - }, - { - "package": "MetaTextKit", - "repositoryURL": "https://github.com/TwidereProject/MetaTextKit.git", - "state": { - "branch": null, - "revision": "dcd5255d6930c2fab408dc8562c577547e477624", - "version": "2.2.5" - } - }, - { - "package": "Nuke", - "repositoryURL": "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", - "state": { - "branch": null, - "revision": "b59c346a7d536336db3b0f12c72c6e53ee709e16", - "version": "8.0.0" - } - }, - { - "package": "Pageboy", - "repositoryURL": "https://github.com/uias/Pageboy", - "state": { - "branch": null, - "revision": "34ecb6e7c4e0e07494960ab2f7cc9a02293915a6", - "version": "3.6.2" - } - }, - { - "package": "PanModal", - "repositoryURL": "https://github.com/slackhq/PanModal.git", - "state": { - "branch": null, - "revision": "b012aecb6b67a8e46369227f893c12544846613f", - "version": "1.2.7" - } - }, - { - "package": "SDWebImage", - "repositoryURL": "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", - "state": { - "branch": null, - "revision": "f504716c27d2e5d4144fa4794b12129301d17729", - "version": "1.0.3" - } - }, - { - "package": "swift-nio", - "repositoryURL": "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", - "state": { - "branch": null, - "revision": "37760e9a52030bb9011972c5213c3350fa9d41fd", - "version": "1.0.0" - } - }, - { - "package": "SwiftSoup", - "repositoryURL": "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", - "state": { - "branch": null, - "revision": "f2616860a41f9d9932da412a8978fec79c06fe24", - "version": "0.1.4" - } - }, - { - "package": "SwiftyJSON", - "repositoryURL": "https://github.com/SwiftyJSON/SwiftyJSON.git", - "state": { - "branch": null, - "revision": "b3dcd7dbd0d488e1a7077cb33b00f2083e382f07", - "version": "5.0.1" - } - }, - { - "package": "TabBarPager", - "repositoryURL": "https://github.com/TwidereProject/TabBarPager.git", - "state": { - "branch": null, - "revision": "488aa66d157a648901b61721212c0dec23d27ee5", - "version": "0.1.0" - } - }, - { - "package": "Tabman", - "repositoryURL": "https://github.com/uias/Tabman", - "state": { - "branch": null, - "revision": "4a4f7c755b875ffd4f9ef10d67a67883669d2465", - "version": "2.13.0" - } - }, - { - "package": "ThirdPartyMailer", - "repositoryURL": "https://github.com/vtourraine/ThirdPartyMailer.git", - "state": { - "branch": null, - "revision": "44c1cfaa6969963f22691aa67f88a69e3b6d651f", - "version": "2.1.0" - } - }, - { - "package": "TOCropViewController", - "repositoryURL": "https://github.com/TimOliver/TOCropViewController.git", - "state": { - "branch": null, - "revision": "d0470491f56e734731bbf77991944c0dfdee3e0e", - "version": "2.6.1" - } - }, - { - "package": "UIHostingConfigurationBackport", - "repositoryURL": "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", - "state": { - "branch": null, - "revision": "20f513ded04a040cdf5467f0891849b1763ede3b", - "version": "1.4.1" - } + "pins" : [ + { + "identity" : "alamofire", + "kind" : "remoteSourceControl", + "location" : "https://github.com/Alamofire/Alamofire.git", + "state" : { + "revision" : "354dda32d89fc8cd4f5c46487f64957d355f53d8", + "version" : "5.6.1" } - ] - }, - "version": 1 + }, + { + "identity" : "alamofireimage", + "kind" : "remoteSourceControl", + "location" : "https://github.com/Alamofire/AlamofireImage.git", + "state" : { + "revision" : "98cbb00ce0ec5fc8e52a5b50a6bfc08d3e5aee10", + "version" : "4.2.0" + } + }, + { + "identity" : "commonoslog", + "kind" : "remoteSourceControl", + "location" : "https://github.com/MainasuK/CommonOSLog", + "state" : { + "revision" : "c121624a30698e9886efe38aebb36ff51c01b6c2", + "version" : "0.1.1" + } + }, + { + "identity" : "faviconfinder", + "kind" : "remoteSourceControl", + "location" : "https://github.com/will-lumley/FaviconFinder.git", + "state" : { + "revision" : "1f74844f77f79b95c0bb0130b3a87d4f340e6d3a", + "version" : "3.3.0" + } + }, + { + "identity" : "flanimatedimage", + "kind" : "remoteSourceControl", + "location" : "https://github.com/Flipboard/FLAnimatedImage.git", + "state" : { + "revision" : "e7f9fd4681ae41bf6f3056db08af4f401d61da52", + "version" : "1.0.16" + } + }, + { + "identity" : "fpsindicator", + "kind" : "remoteSourceControl", + "location" : "https://github.com/MainasuK/FPSIndicator.git", + "state" : { + "revision" : "e4a5067ccd5293b024c767f09e51056afd4a4796", + "version" : "1.1.0" + } + }, + { + "identity" : "fuzi", + "kind" : "remoteSourceControl", + "location" : "https://github.com/cezheng/Fuzi.git", + "state" : { + "revision" : "f08c8323da21e985f3772610753bcfc652c2103f", + "version" : "3.1.3" + } + }, + { + "identity" : "keychainaccess", + "kind" : "remoteSourceControl", + "location" : "https://github.com/kishikawakatsumi/KeychainAccess.git", + "state" : { + "revision" : "84e546727d66f1adc5439debad16270d0fdd04e7", + "version" : "4.2.2" + } + }, + { + "identity" : "metatextkit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/TwidereProject/MetaTextKit.git", + "state" : { + "revision" : "dcd5255d6930c2fab408dc8562c577547e477624", + "version" : "2.2.5" + } + }, + { + "identity" : "nuke", + "kind" : "remoteSourceControl", + "location" : "https://github.com/kean/Nuke.git", + "state" : { + "revision" : "0ea7545b5c918285aacc044dc75048625c8257cc", + "version" : "10.8.0" + } + }, + { + "identity" : "nuke-flanimatedimage-plugin", + "kind" : "remoteSourceControl", + "location" : "https://github.com/kean/Nuke-FLAnimatedImage-Plugin.git", + "state" : { + "revision" : "b59c346a7d536336db3b0f12c72c6e53ee709e16", + "version" : "8.0.0" + } + }, + { + "identity" : "pageboy", + "kind" : "remoteSourceControl", + "location" : "https://github.com/uias/Pageboy", + "state" : { + "revision" : "34ecb6e7c4e0e07494960ab2f7cc9a02293915a6", + "version" : "3.6.2" + } + }, + { + "identity" : "panmodal", + "kind" : "remoteSourceControl", + "location" : "https://github.com/slackhq/PanModal.git", + "state" : { + "revision" : "b012aecb6b67a8e46369227f893c12544846613f", + "version" : "1.2.7" + } + }, + { + "identity" : "sdwebimage", + "kind" : "remoteSourceControl", + "location" : "https://github.com/SDWebImage/SDWebImage.git", + "state" : { + "revision" : "2e63d0061da449ad0ed130768d05dceb1496de44", + "version" : "5.12.5" + } + }, + { + "identity" : "swift-collections", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-collections.git", + "state" : { + "revision" : "f504716c27d2e5d4144fa4794b12129301d17729", + "version" : "1.0.3" + } + }, + { + "identity" : "swift-nio", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-nio.git", + "state" : { + "revision" : "546610d52b19be3e19935e0880bb06b9c03f5cef", + "version" : "1.14.4" + } + }, + { + "identity" : "swift-nio-zlib-support", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-nio-zlib-support.git", + "state" : { + "revision" : "37760e9a52030bb9011972c5213c3350fa9d41fd", + "version" : "1.0.0" + } + }, + { + "identity" : "swiftsoup", + "kind" : "remoteSourceControl", + "location" : "https://github.com/scinfu/SwiftSoup.git", + "state" : { + "revision" : "41e7c263fb8c277e980ebcb9b0b5f6031d3d4886", + "version" : "2.4.2" + } + }, + { + "identity" : "swiftui-introspect", + "kind" : "remoteSourceControl", + "location" : "https://github.com/siteline/SwiftUI-Introspect.git", + "state" : { + "revision" : "f2616860a41f9d9932da412a8978fec79c06fe24", + "version" : "0.1.4" + } + }, + { + "identity" : "swiftyjson", + "kind" : "remoteSourceControl", + "location" : "https://github.com/SwiftyJSON/SwiftyJSON.git", + "state" : { + "revision" : "b3dcd7dbd0d488e1a7077cb33b00f2083e382f07", + "version" : "5.0.1" + } + }, + { + "identity" : "tabbarpager", + "kind" : "remoteSourceControl", + "location" : "https://github.com/TwidereProject/TabBarPager.git", + "state" : { + "revision" : "488aa66d157a648901b61721212c0dec23d27ee5", + "version" : "0.1.0" + } + }, + { + "identity" : "tabman", + "kind" : "remoteSourceControl", + "location" : "https://github.com/uias/Tabman", + "state" : { + "revision" : "4a4f7c755b875ffd4f9ef10d67a67883669d2465", + "version" : "2.13.0" + } + }, + { + "identity" : "thirdpartymailer", + "kind" : "remoteSourceControl", + "location" : "https://github.com/vtourraine/ThirdPartyMailer.git", + "state" : { + "revision" : "44c1cfaa6969963f22691aa67f88a69e3b6d651f", + "version" : "2.1.0" + } + }, + { + "identity" : "tocropviewcontroller", + "kind" : "remoteSourceControl", + "location" : "https://github.com/TimOliver/TOCropViewController.git", + "state" : { + "revision" : "d0470491f56e734731bbf77991944c0dfdee3e0e", + "version" : "2.6.1" + } + }, + { + "identity" : "uihostingconfigurationbackport", + "kind" : "remoteSourceControl", + "location" : "https://github.com/woxtu/UIHostingConfigurationBackport.git", + "state" : { + "revision" : "6091f2d38faa4b24fc2ca0389c651e2f666624a3", + "version" : "0.1.0" + } + }, + { + "identity" : "uitextview-placeholder", + "kind" : "remoteSourceControl", + "location" : "https://github.com/MainasuK/UITextView-Placeholder.git", + "state" : { + "revision" : "20f513ded04a040cdf5467f0891849b1763ede3b", + "version" : "1.4.1" + } + } + ], + "version" : 2 } diff --git a/Mastodon/Coordinator/SceneCoordinator.swift b/Mastodon/Coordinator/SceneCoordinator.swift index 3aeafaabd..ae8d81d25 100644 --- a/Mastodon/Coordinator/SceneCoordinator.swift +++ b/Mastodon/Coordinator/SceneCoordinator.swift @@ -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,100 +45,83 @@ final public class SceneCoordinator { appContext.notificationService.requestRevealNotificationPublisher .receive(on: DispatchQueue.main) - .compactMap { [weak self] pushNotification -> AnyPublisher 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() - } - - 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() - } else { - // switch to notification's account - let request = MastodonAuthentication.sortedFetchRequest - request.predicate = MastodonAuthentication.predicate(userAccessToken: accessToken) - request.returnsObjectsAsFaults = false - request.fetchLimit = 1 - do { - guard let authentication = try appContext.managedObjectContext.fetch(request).first else { - return Just(nil).eraseToAnyPublisher() - } - 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 - 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 + .sink(receiveValue: { [weak self] pushNotification in guard let self = self else { return } - guard let pushNotification = pushNotification else { return } - - // 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 } - - // Note: - // show (push) on phone and pad - let from: UIViewController? = { - if let splitViewController = self.splitViewController { - if splitViewController.compactMainTabBarViewController.topMost?.view.window != nil { - // compact - return splitViewController.compactMainTabBarViewController.topMost - } else { - // expand - return splitViewController.contentSplitViewController.mainTabBarController.topMost + 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 + } else { + // switch to notification's account + let request = MastodonAuthentication.sortedFetchRequest + request.predicate = MastodonAuthentication.predicate(userAccessToken: accessToken) + request.returnsObjectsAsFaults = false + request.fetchLimit = 1 + do { + guard let authentication = try appContext.managedObjectContext.fetch(request).first else { + return } - } else { - return self.tabBarController.topMost + let domain = authentication.domain + let userID = authentication.userID + let isSuccess = try await appContext.authenticationService.activeMastodonUser(domain: domain, userID: userID) + guard isSuccess else { return } + + self.setup() + 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 } + + // Note: + // show (push) on phone and pad + let from: UIViewController? = { + if let splitViewController = self.splitViewController { + if splitViewController.compactMainTabBarViewController.topMost?.view.window != nil { + // compact + return splitViewController.compactMainTabBarViewController.topMost + } else { + // expand + return splitViewController.contentSplitViewController.mainTabBarController.topMost + } + } else { + return self.tabBarController.topMost + } + }() + + // 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, 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, 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 } - }() - - // show notification related content - guard let type = Mastodon.Entity.Notification.NotificationType(rawValue: pushNotification.notificationType) 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) - 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) - case ._other: - assertionFailure() - break } - } // end DispatchQueue.main.async - } + } // 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() diff --git a/Mastodon/Diffiable/Compose/ComposeStatusAttachmentSection.swift b/Mastodon/Diffiable/Compose/ComposeStatusAttachmentSection.swift index 4de7653a5..2e2a94206 100644 --- a/Mastodon/Diffiable/Compose/ComposeStatusAttachmentSection.swift +++ b/Mastodon/Diffiable/Compose/ComposeStatusAttachmentSection.swift @@ -10,4 +10,3 @@ import Foundation enum ComposeStatusAttachmentSection: Hashable { case main } - diff --git a/Mastodon/Diffiable/Discovery/DiscoverySection.swift b/Mastodon/Diffiable/Discovery/DiscoverySection.swift index 2910171d1..225b6f46a 100644 --- a/Mastodon/Diffiable/Discovery/DiscoverySection.swift +++ b/Mastodon/Diffiable/Discovery/DiscoverySection.swift @@ -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 diff --git a/Mastodon/Diffiable/Notification/NotificationSection.swift b/Mastodon/Diffiable/Notification/NotificationSection.swift index 6f08a0252..a67d407ee 100644 --- a/Mastodon/Diffiable/Notification/NotificationSection.swift +++ b/Mastodon/Diffiable/Notification/NotificationSection.swift @@ -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, diff --git a/Mastodon/Diffiable/RecommandAccount/RecommendAccountSection.swift b/Mastodon/Diffiable/RecommandAccount/RecommendAccountSection.swift index fc2d68044..e5aa0a605 100644 --- a/Mastodon/Diffiable/RecommandAccount/RecommendAccountSection.swift +++ b/Mastodon/Diffiable/RecommandAccount/RecommendAccountSection.swift @@ -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 diff --git a/Mastodon/Diffiable/Report/ReportSection.swift b/Mastodon/Diffiable/Report/ReportSection.swift index 6513c1249..b815975b0 100644 --- a/Mastodon/Diffiable/Report/ReportSection.swift +++ b/Mastodon/Diffiable/Report/ReportSection.swift @@ -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, diff --git a/Mastodon/Diffiable/Search/SearchResultSection.swift b/Mastodon/Diffiable/Search/SearchResultSection.swift index b7fb09df7..8a5d7e75f 100644 --- a/Mastodon/Diffiable/Search/SearchResultSection.swift +++ b/Mastodon/Diffiable/Search/SearchResultSection.swift @@ -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, @@ -120,7 +119,7 @@ extension SearchResultSection { cell: UserTableViewCell, viewModel: UserTableViewCell.ViewModel, configuration: Configuration - ) { + ) { cell.configure( tableView: tableView, viewModel: viewModel, diff --git a/Mastodon/Diffiable/Status/StatusSection.swift b/Mastodon/Diffiable/Status/StatusSection.swift index 08b55bc69..38b8e641f 100644 --- a/Mastodon/Diffiable/Status/StatusSection.swift +++ b/Mastodon/Diffiable/Status/StatusSection.swift @@ -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 = .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, diff --git a/Mastodon/Protocol/Provider/DataSourceFacade+Block.swift b/Mastodon/Protocol/Provider/DataSourceFacade+Block.swift index 7166cb39b..2747f4735 100644 --- a/Mastodon/Protocol/Provider/DataSourceFacade+Block.swift +++ b/Mastodon/Protocol/Provider/DataSourceFacade+Block.swift @@ -11,16 +11,15 @@ import MastodonCore extension DataSourceFacade { static func responseToUserBlockAction( - dependency: NeedsDependency, - user: ManagedObjectRecord, - authenticationBox: MastodonAuthenticationBox + dependency: NeedsDependency & AuthContextProvider, + user: ManagedObjectRecord ) 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 } diff --git a/Mastodon/Protocol/Provider/DataSourceFacade+Bookmark.swift b/Mastodon/Protocol/Provider/DataSourceFacade+Bookmark.swift index 93da15271..b7a793551 100644 --- a/Mastodon/Protocol/Provider/DataSourceFacade+Bookmark.swift +++ b/Mastodon/Protocol/Provider/DataSourceFacade+Bookmark.swift @@ -12,16 +12,15 @@ import MastodonCore extension DataSourceFacade { public static func responseToStatusBookmarkAction( - provider: DataSourceProvider, - status: ManagedObjectRecord, - authenticationBox: MastodonAuthenticationBox + provider: DataSourceProvider & AuthContextProvider, + status: ManagedObjectRecord ) async throws { let selectionFeedbackGenerator = await UISelectionFeedbackGenerator() await selectionFeedbackGenerator.selectionChanged() _ = try await provider.context.apiService.bookmark( record: status, - authenticationBox: authenticationBox + authenticationBox: provider.authContext.mastodonAuthenticationBox ) } } diff --git a/Mastodon/Protocol/Provider/DataSourceFacade+Favorite.swift b/Mastodon/Protocol/Provider/DataSourceFacade+Favorite.swift index 71c02828f..92945b9ee 100644 --- a/Mastodon/Protocol/Provider/DataSourceFacade+Favorite.swift +++ b/Mastodon/Protocol/Provider/DataSourceFacade+Favorite.swift @@ -12,16 +12,15 @@ import MastodonCore extension DataSourceFacade { public static func responseToStatusFavoriteAction( - provider: DataSourceProvider, - status: ManagedObjectRecord, - authenticationBox: MastodonAuthenticationBox + provider: DataSourceProvider & AuthContextProvider, + status: ManagedObjectRecord ) async throws { let selectionFeedbackGenerator = await UISelectionFeedbackGenerator() await selectionFeedbackGenerator.selectionChanged() _ = try await provider.context.apiService.favorite( record: status, - authenticationBox: authenticationBox + authenticationBox: provider.authContext.mastodonAuthenticationBox ) } } diff --git a/Mastodon/Protocol/Provider/DataSourceFacade+Follow.swift b/Mastodon/Protocol/Provider/DataSourceFacade+Follow.swift index cd29d2eca..c6e40e7d9 100644 --- a/Mastodon/Protocol/Provider/DataSourceFacade+Follow.swift +++ b/Mastodon/Protocol/Provider/DataSourceFacade+Follow.swift @@ -14,26 +14,24 @@ import MastodonLocalization extension DataSourceFacade { static func responseToUserFollowAction( - dependency: NeedsDependency, - user: ManagedObjectRecord, - authenticationBox: MastodonAuthenticationBox + dependency: NeedsDependency & AuthContextProvider, + user: ManagedObjectRecord ) 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, - 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 diff --git a/Mastodon/Protocol/Provider/DataSourceFacade+Hashtag.swift b/Mastodon/Protocol/Provider/DataSourceFacade+Hashtag.swift index 7abde62fe..43d6b954b 100644 --- a/Mastodon/Protocol/Provider/DataSourceFacade+Hashtag.swift +++ b/Mastodon/Protocol/Provider/DataSourceFacade+Hashtag.swift @@ -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 ) async { let managedObjectContext = provider.context.managedObjectContext @@ -55,6 +57,7 @@ extension DataSourceFacade { let hashtagTimelineViewModel = HashtagTimelineViewModel( context: provider.context, + authContext: provider.authContext, hashtag: name ) diff --git a/Mastodon/Protocol/Provider/DataSourceFacade+Meta.swift b/Mastodon/Protocol/Provider/DataSourceFacade+Meta.swift index 7e376ed0f..7e0ed37fc 100644 --- a/Mastodon/Protocol/Provider/DataSourceFacade+Meta.swift +++ b/Mastodon/Protocol/Provider/DataSourceFacade+Meta.swift @@ -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, meta: Meta @@ -33,7 +34,7 @@ extension DataSourceFacade { } static func responseToMetaTextAction( - provider: DataSourceProvider, + provider: DataSourceProvider & AuthContextProvider, status: ManagedObjectRecord, 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( diff --git a/Mastodon/Protocol/Provider/DataSourceFacade+Mute.swift b/Mastodon/Protocol/Provider/DataSourceFacade+Mute.swift index b48fbf462..1db94bd4f 100644 --- a/Mastodon/Protocol/Provider/DataSourceFacade+Mute.swift +++ b/Mastodon/Protocol/Provider/DataSourceFacade+Mute.swift @@ -11,16 +11,15 @@ import MastodonCore extension DataSourceFacade { static func responseToUserMuteAction( - dependency: NeedsDependency, - user: ManagedObjectRecord, - authenticationBox: MastodonAuthenticationBox + dependency: NeedsDependency & AuthContextProvider, + user: ManagedObjectRecord ) 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 } diff --git a/Mastodon/Protocol/Provider/DataSourceFacade+Profile.swift b/Mastodon/Protocol/Provider/DataSourceFacade+Profile.swift index 66259a099..ef01b8394 100644 --- a/Mastodon/Protocol/Provider/DataSourceFacade+Profile.swift +++ b/Mastodon/Protocol/Provider/DataSourceFacade+Profile.swift @@ -7,11 +7,12 @@ import UIKit import CoreDataStack +import MastodonCore extension DataSourceFacade { static func coordinateToProfileScene( - provider: DataSourceProvider, + provider: DataSourceProvider & AuthContextProvider, target: StatusTarget, status: ManagedObjectRecord ) async { @@ -32,7 +33,7 @@ extension DataSourceFacade { @MainActor static func coordinateToProfileScene( - provider: DataSourceProvider, + provider: DataSourceProvider & AuthContextProvider, user: ManagedObjectRecord ) 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, 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) } }() diff --git a/Mastodon/Protocol/Provider/DataSourceFacade+Reblog.swift b/Mastodon/Protocol/Provider/DataSourceFacade+Reblog.swift index 283a21dc8..ff3e95820 100644 --- a/Mastodon/Protocol/Provider/DataSourceFacade+Reblog.swift +++ b/Mastodon/Protocol/Provider/DataSourceFacade+Reblog.swift @@ -12,16 +12,15 @@ import MastodonUI extension DataSourceFacade { static func responseToStatusReblogAction( - provider: DataSourceProvider, - status: ManagedObjectRecord, - authenticationBox: MastodonAuthenticationBox + provider: DataSourceProvider & AuthContextProvider, + status: ManagedObjectRecord ) 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 } diff --git a/Mastodon/Protocol/Provider/DataSourceFacade+SearchHistory.swift b/Mastodon/Protocol/Provider/DataSourceFacade+SearchHistory.swift index 25ffd4b8e..18d238c02 100644 --- a/Mastodon/Protocol/Provider/DataSourceFacade+SearchHistory.swift +++ b/Mastodon/Protocol/Provider/DataSourceFacade+SearchHistory.swift @@ -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( diff --git a/Mastodon/Protocol/Provider/DataSourceFacade+Status.swift b/Mastodon/Protocol/Provider/DataSourceFacade+Status.swift index 25ab53103..aecfe6a8d 100644 --- a/Mastodon/Protocol/Provider/DataSourceFacade+Status.swift +++ b/Mastodon/Protocol/Provider/DataSourceFacade+Status.swift @@ -15,13 +15,12 @@ import MastodonLocalization extension DataSourceFacade { static func responseToDeleteStatus( - dependency: NeedsDependency, - status: ManagedObjectRecord, - authenticationBox: MastodonAuthenticationBox + dependency: NeedsDependency & AuthContextProvider, + status: ManagedObjectRecord ) 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, 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 } diff --git a/Mastodon/Protocol/Provider/DataSourceFacade+Thread.swift b/Mastodon/Protocol/Provider/DataSourceFacade+Thread.swift index 269504215..41f5d58de 100644 --- a/Mastodon/Protocol/Provider/DataSourceFacade+Thread.swift +++ b/Mastodon/Protocol/Provider/DataSourceFacade+Thread.swift @@ -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 ) 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 diff --git a/Mastodon/Protocol/Provider/DataSourceProvider+NotificationTableViewCellDelegate.swift b/Mastodon/Protocol/Provider/DataSourceProvider+NotificationTableViewCellDelegate.swift index dab46bba8..e868f418f 100644 --- a/Mastodon/Protocol/Provider/DataSourceProvider+NotificationTableViewCellDelegate.swift +++ b/Mastodon/Protocol/Provider/DataSourceProvider+NotificationTableViewCellDelegate.swift @@ -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) diff --git a/Mastodon/Protocol/Provider/DataSourceProvider+StatusTableViewCellDelegate.swift b/Mastodon/Protocol/Provider/DataSourceProvider+StatusTableViewCellDelegate.swift index d02edcc42..82c25d040 100644 --- a/Mastodon/Protocol/Provider/DataSourceProvider+StatusTableViewCellDelegate.swift +++ b/Mastodon/Protocol/Provider/DataSourceProvider+StatusTableViewCellDelegate.swift @@ -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) diff --git a/Mastodon/Protocol/Provider/DataSourceProvider+StatusTableViewControllerNavigateable.swift b/Mastodon/Protocol/Provider/DataSourceProvider+StatusTableViewControllerNavigateable.swift index c80121e98..390f246d4 100644 --- a/Mastodon/Protocol/Provider/DataSourceProvider+StatusTableViewControllerNavigateable.swift +++ b/Mastodon/Protocol/Provider/DataSourceProvider+StatusTableViewControllerNavigateable.swift @@ -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? { @@ -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() diff --git a/Mastodon/Protocol/Provider/DataSourceProvider+TableViewControllerNavigateable.swift b/Mastodon/Protocol/Provider/DataSourceProvider+TableViewControllerNavigateable.swift index 50fa17866..35ef7761e 100644 --- a/Mastodon/Protocol/Provider/DataSourceProvider+TableViewControllerNavigateable.swift +++ b/Mastodon/Protocol/Provider/DataSourceProvider+TableViewControllerNavigateable.swift @@ -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) diff --git a/Mastodon/Protocol/Provider/DataSourceProvider+UITableViewDelegate.swift b/Mastodon/Protocol/Provider/DataSourceProvider+UITableViewDelegate.swift index b62064a6f..3a71e5346 100644 --- a/Mastodon/Protocol/Provider/DataSourceProvider+UITableViewDelegate.swift +++ b/Mastodon/Protocol/Provider/DataSourceProvider+UITableViewDelegate.swift @@ -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)") diff --git a/Mastodon/Scene/Account/AccountListViewModel.swift b/Mastodon/Scene/Account/AccountListViewModel.swift index 3149b201d..17eab07ea 100644 --- a/Mastodon/Scene/Account/AccountListViewModel.swift +++ b/Mastodon/Scene/Account/AccountListViewModel.swift @@ -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() // input let context: AppContext + let authContext: AuthContext + let mastodonAuthenticationFetchedResultsController: NSFetchedResultsController // output - let authentications = CurrentValueSubject<[Item], Never>([]) - let activeMastodonUserObjectID = CurrentValueSubject(nil) + @Published var authentications: [ManagedObjectRecord] = [] + @Published var items: [Item] = [] + let dataSourceDidUpdate = PassthroughSubject() var diffableDataSource: UITableViewDiffableDataSource! - init(context: AppContext) { + init(context: AppContext, authContext: AuthContext) { self.context = context + 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 + ) + return controller + }() + super.init() + // end init + + mastodonAuthenticationFetchedResultsController.delegate = self - Publishers.CombineLatest( - context.authenticationService.mastodonAuthentications, - context.authenticationService.activeMastodonAuthentication - ) - .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) - - authentications + $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() 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) 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 - AccountListViewModel.configure( - cell: cell, - authentication: authentication, - activeMastodonUserObjectID: self.activeMastodonUserObjectID.eraseToAnyPublisher() - ) + if let authentication = record.object(in: managedObjectContext), + let activeAuthentication = self.authContext.mastodonAuthenticationBox.authenticationRecord.object(in: managedObjectContext) + { + AccountListViewModel.configure( + cell: cell, + authentication: authentication, + 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 + 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 - cell.tintColor = .label - cell.checkmarkImageView.isHidden = !isCurrentUser - if isCurrentUser { - cell.accessibilityTraits.insert(.selected) - } else { - cell.accessibilityTraits.remove(.selected) - } - } - .store(in: &cell.disposeBag) + let isActive = activeAuthentication.userID == authentication.userID + cell.tintColor = .label + cell.checkmarkImageView.isHidden = !isActive + if isActive { + cell.accessibilityTraits.insert(.selected) + } else { + cell.accessibilityTraits.remove(.selected) + } cell.accessibilityLabel = [ cell.nameLabel.text, @@ -161,3 +163,21 @@ extension AccountListViewModel { .joined(separator: " ") } } + +// MARK: - NSFetchedResultsControllerDelegate +extension AccountListViewModel: NSFetchedResultsControllerDelegate { + + public func controllerWillChangeContent(_ controller: NSFetchedResultsController) { + os_log("%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function) + } + + public func controllerDidChangeContent(_ controller: NSFetchedResultsController) { + guard controller === mastodonAuthenticationFetchedResultsController else { + assertionFailure() + return + } + + authentications = mastodonAuthenticationFetchedResultsController.fetchedObjects?.compactMap { $0.asRecrod } ?? [] + } + +} diff --git a/Mastodon/Scene/Account/AccountViewController.swift b/Mastodon/Scene/Account/AccountViewController.swift index 6a97c6427..ed9f883b4 100644 --- a/Mastodon/Scene/Account/AccountViewController.swift +++ b/Mastodon/Scene/Account/AccountViewController.swift @@ -22,7 +22,7 @@ final class AccountListViewController: UIViewController, NeedsDependency { weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } } var disposeBag = Set() - 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 } - self.coordinator.setup() - } - .store(in: &disposeBag) + 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() + } // end Task case .addAccount: // TODO: add dismiss entry for welcome scene coordinator.present(scene: .welcome, from: self, transition: .modal(animated: true, completion: nil)) diff --git a/Mastodon/Scene/Compose/AutoComplete/AutoCompleteViewModel+State.swift b/Mastodon/Scene/Compose/AutoComplete/AutoCompleteViewModel+State.swift index 29016fafb..1d46fb214 100644 --- a/Mastodon/Scene/Compose/AutoComplete/AutoCompleteViewModel+State.swift +++ b/Mastodon/Scene/Compose/AutoComplete/AutoCompleteViewModel+State.swift @@ -133,11 +133,6 @@ extension AutoCompleteViewModel.State { await enter(state: Fail.self) 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) diff --git a/Mastodon/Scene/Compose/AutoComplete/AutoCompleteViewModel.swift b/Mastodon/Scene/Compose/AutoComplete/AutoCompleteViewModel.swift index ecc234612..61715cd63 100644 --- a/Mastodon/Scene/Compose/AutoComplete/AutoCompleteViewModel.swift +++ b/Mastodon/Scene/Compose/AutoComplete/AutoCompleteViewModel.swift @@ -17,6 +17,7 @@ final class AutoCompleteViewModel { // input let context: AppContext + let authContext: AuthContext public let inputText = CurrentValueSubject("") // contains "@" or "#" prefix public let symbolBoundingRect = CurrentValueSubject(.zero) public let customEmojiViewModel = CurrentValueSubject(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) diff --git a/Mastodon/Scene/Compose/ComposeViewController.swift b/Mastodon/Scene/Compose/ComposeViewController.swift index 619f6efb1..b9605bb78 100644 --- a/Mastodon/Scene/Compose/ComposeViewController.swift +++ b/Mastodon/Scene/Compose/ComposeViewController.swift @@ -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 diff --git a/Mastodon/Scene/Compose/ComposeViewModel.swift b/Mastodon/Scene/Compose/ComposeViewModel.swift index 35cc965ed..de088c68b 100644 --- a/Mastodon/Scene/Compose/ComposeViewModel.swift +++ b/Mastodon/Scene/Compose/ComposeViewModel.swift @@ -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 diff --git a/Mastodon/Scene/Discovery/Community/DiscoveryCommunityViewController.swift b/Mastodon/Scene/Discovery/Community/DiscoveryCommunityViewController.swift index b8c86974b..d592c3033 100644 --- a/Mastodon/Scene/Discovery/Community/DiscoveryCommunityViewController.swift +++ b/Mastodon/Scene/Discovery/Community/DiscoveryCommunityViewController.swift @@ -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 diff --git a/Mastodon/Scene/Discovery/Community/DiscoveryCommunityViewModel+Diffable.swift b/Mastodon/Scene/Discovery/Community/DiscoveryCommunityViewModel+Diffable.swift index 26335ec3d..64b4d3b6a 100644 --- a/Mastodon/Scene/Discovery/Community/DiscoveryCommunityViewModel+Diffable.swift +++ b/Mastodon/Scene/Discovery/Community/DiscoveryCommunityViewModel+Diffable.swift @@ -18,6 +18,7 @@ extension DiscoveryCommunityViewModel { tableView: tableView, context: context, configuration: StatusSection.Configuration( + authContext: authContext, statusTableViewCellDelegate: statusTableViewCellDelegate, timelineMiddleLoaderTableViewCellDelegate: nil, filterContext: .none, diff --git a/Mastodon/Scene/Discovery/Community/DiscoveryCommunityViewModel+State.swift b/Mastodon/Scene/Discovery/Community/DiscoveryCommunityViewModel+State.swift index b7b078c7a..cbf292e3b 100644 --- a/Mastodon/Scene/Discovery/Community/DiscoveryCommunityViewModel+State.swift +++ b/Mastodon/Scene/Discovery/Community/DiscoveryCommunityViewModel+State.swift @@ -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 diff --git a/Mastodon/Scene/Discovery/Community/DiscoveryCommunityViewModel.swift b/Mastodon/Scene/Discovery/Community/DiscoveryCommunityViewModel.swift index 4c01cb0bc..eaf8646c4 100644 --- a/Mastodon/Scene/Discovery/Community/DiscoveryCommunityViewModel.swift +++ b/Mastodon/Scene/Discovery/Community/DiscoveryCommunityViewModel.swift @@ -22,6 +22,7 @@ final class DiscoveryCommunityViewModel { // input let context: AppContext + let authContext: AuthContext let viewDidAppeared = PassthroughSubject() let statusFetchedResultsController: StatusFetchedResultsController let listBatchFetchViewModel = ListBatchFetchViewModel() @@ -43,20 +44,15 @@ final class DiscoveryCommunityViewModel { let didLoadLatest = PassthroughSubject() - 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 { diff --git a/Mastodon/Scene/Discovery/DiscoveryViewController.swift b/Mastodon/Scene/Discovery/DiscoveryViewController.swift index 3fc2e8944..33bbefaef 100644 --- a/Mastodon/Scene/Discovery/DiscoveryViewController.swift +++ b/Mastodon/Scene/Discovery/DiscoveryViewController.swift @@ -25,11 +25,8 @@ 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() diff --git a/Mastodon/Scene/Discovery/DiscoveryViewModel.swift b/Mastodon/Scene/Discovery/DiscoveryViewModel.swift index d91d9eee1..244a2e8d4 100644 --- a/Mastodon/Scene/Discovery/DiscoveryViewModel.swift +++ b/Mastodon/Scene/Discovery/DiscoveryViewModel.swift @@ -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 = [ diff --git a/Mastodon/Scene/Discovery/ForYou/DiscoveryForYouViewController.swift b/Mastodon/Scene/Discovery/ForYou/DiscoveryForYouViewController.swift index aa4d45f6d..ce1aadbb4 100644 --- a/Mastodon/Scene/Discovery/ForYou/DiscoveryForYouViewController.swift +++ b/Mastodon/Scene/Discovery/ForYou/DiscoveryForYouViewController.swift @@ -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 diff --git a/Mastodon/Scene/Discovery/ForYou/DiscoveryForYouViewModel+Diffable.swift b/Mastodon/Scene/Discovery/ForYou/DiscoveryForYouViewModel+Diffable.swift index f93b4c0bd..af8d6ff47 100644 --- a/Mastodon/Scene/Discovery/ForYou/DiscoveryForYouViewModel+Diffable.swift +++ b/Mastodon/Scene/Discovery/ForYou/DiscoveryForYouViewModel+Diffable.swift @@ -19,6 +19,7 @@ extension DiscoveryForYouViewModel { tableView: tableView, context: context, configuration: DiscoverySection.Configuration( + authContext: authContext, profileCardTableViewCellDelegate: profileCardTableViewCellDelegate, familiarFollowers: $familiarFollowers ) diff --git a/Mastodon/Scene/Discovery/ForYou/DiscoveryForYouViewModel.swift b/Mastodon/Scene/Discovery/ForYou/DiscoveryForYouViewModel.swift index 5073ae200..89122c06e 100644 --- a/Mastodon/Scene/Discovery/ForYou/DiscoveryForYouViewModel.swift +++ b/Mastodon/Scene/Discovery/ForYou/DiscoveryForYouViewModel.swift @@ -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? let didLoadLatest = PassthroughSubject() - 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 diff --git a/Mastodon/Scene/Discovery/Hashtags/DiscoveryHashtagsViewController.swift b/Mastodon/Scene/Discovery/Hashtags/DiscoveryHashtagsViewController.swift index 8985af2de..e315f04ee 100644 --- a/Mastodon/Scene/Discovery/Hashtags/DiscoveryHashtagsViewController.swift +++ b/Mastodon/Scene/Discovery/Hashtags/DiscoveryHashtagsViewController.swift @@ -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, diff --git a/Mastodon/Scene/Discovery/Hashtags/DiscoveryHashtagsViewModel+Diffable.swift b/Mastodon/Scene/Discovery/Hashtags/DiscoveryHashtagsViewModel+Diffable.swift index 19241d2e6..67362d19e 100644 --- a/Mastodon/Scene/Discovery/Hashtags/DiscoveryHashtagsViewModel+Diffable.swift +++ b/Mastodon/Scene/Discovery/Hashtags/DiscoveryHashtagsViewModel+Diffable.swift @@ -15,7 +15,7 @@ extension DiscoveryHashtagsViewModel { diffableDataSource = DiscoverySection.diffableDataSource( tableView: tableView, context: context, - configuration: DiscoverySection.Configuration() + configuration: DiscoverySection.Configuration(authContext: authContext) ) var snapshot = NSDiffableDataSourceSnapshot() diff --git a/Mastodon/Scene/Discovery/Hashtags/DiscoveryHashtagsViewModel.swift b/Mastodon/Scene/Discovery/Hashtags/DiscoveryHashtagsViewModel.swift index 5e931063a..7727a2358 100644 --- a/Mastodon/Scene/Discovery/Hashtags/DiscoveryHashtagsViewModel.swift +++ b/Mastodon/Scene/Discovery/Hashtags/DiscoveryHashtagsViewModel.swift @@ -22,41 +22,37 @@ final class DiscoveryHashtagsViewModel { // input let context: AppContext + let authContext: AuthContext let viewDidAppeared = PassthroughSubject() // output var diffableDataSource: UITableViewDiffableDataSource? @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) - } - .retry(3) - .map { response in Result, Error> { response } } - .catch { error in Just(Result, Error> { throw error }) } - .receive(on: DispatchQueue.main) - .sink { [weak self] result in - guard let self = self else { return } - switch result { - case .success(let response): - self.hashtags = response.value.filter { !$0.name.isEmpty } - case .failure: - break + viewDidAppeared + .throttle(for: 3, scheduler: DispatchQueue.main, latest: true) + .asyncMap { authenticationBox in + try await context.apiService.trendHashtags(domain: authContext.mastodonAuthenticationBox.domain, query: nil) } - } - .store(in: &disposeBag) + .retry(3) + .map { response in Result, Error> { response } } + .catch { error in Just(Result, Error> { throw error }) } + .receive(on: DispatchQueue.main) + .sink { [weak self] result in + guard let self = self else { return } + switch result { + case .success(let response): + self.hashtags = response.value.filter { !$0.name.isEmpty } + case .failure: + break + } + } + .store(in: &disposeBag) } deinit { @@ -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)") } diff --git a/Mastodon/Scene/Discovery/News/DiscoveryNewsViewModel+Diffable.swift b/Mastodon/Scene/Discovery/News/DiscoveryNewsViewModel+Diffable.swift index ab3634a3f..11334dee8 100644 --- a/Mastodon/Scene/Discovery/News/DiscoveryNewsViewModel+Diffable.swift +++ b/Mastodon/Scene/Discovery/News/DiscoveryNewsViewModel+Diffable.swift @@ -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) diff --git a/Mastodon/Scene/Discovery/News/DiscoveryNewsViewModel+State.swift b/Mastodon/Scene/Discovery/News/DiscoveryNewsViewModel+State.swift index 92b84d176..09c3c57bb 100644 --- a/Mastodon/Scene/Discovery/News/DiscoveryNewsViewModel+State.swift +++ b/Mastodon/Scene/Discovery/News/DiscoveryNewsViewModel+State.swift @@ -136,11 +136,6 @@ extension DiscoveryNewsViewModel.State { default: break } - - guard let authenticationBox = viewModel.context.authenticationService.activeMastodonAuthenticationBox.value else { - stateMachine.enter(Fail.self) - return - } let offset = self.offset let isReloading = offset == nil @@ -148,7 +143,7 @@ extension DiscoveryNewsViewModel.State { 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 diff --git a/Mastodon/Scene/Discovery/News/DiscoveryNewsViewModel.swift b/Mastodon/Scene/Discovery/News/DiscoveryNewsViewModel.swift index 35af8f962..d440e9528 100644 --- a/Mastodon/Scene/Discovery/News/DiscoveryNewsViewModel.swift +++ b/Mastodon/Scene/Discovery/News/DiscoveryNewsViewModel.swift @@ -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() @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 { diff --git a/Mastodon/Scene/Discovery/Posts/DiscoveryPostsViewController.swift b/Mastodon/Scene/Discovery/Posts/DiscoveryPostsViewController.swift index 96f20dbcc..ee3538885 100644 --- a/Mastodon/Scene/Discovery/Posts/DiscoveryPostsViewController.swift +++ b/Mastodon/Scene/Discovery/Posts/DiscoveryPostsViewController.swift @@ -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 diff --git a/Mastodon/Scene/Discovery/Posts/DiscoveryPostsViewModel+Diffable.swift b/Mastodon/Scene/Discovery/Posts/DiscoveryPostsViewModel+Diffable.swift index 5c82384c7..afa0594d5 100644 --- a/Mastodon/Scene/Discovery/Posts/DiscoveryPostsViewModel+Diffable.swift +++ b/Mastodon/Scene/Discovery/Posts/DiscoveryPostsViewModel+Diffable.swift @@ -18,6 +18,7 @@ extension DiscoveryPostsViewModel { tableView: tableView, context: context, configuration: StatusSection.Configuration( + authContext: authContext, statusTableViewCellDelegate: statusTableViewCellDelegate, timelineMiddleLoaderTableViewCellDelegate: nil, filterContext: .none, diff --git a/Mastodon/Scene/Discovery/Posts/DiscoveryPostsViewModel+State.swift b/Mastodon/Scene/Discovery/Posts/DiscoveryPostsViewModel+State.swift index 2e5ae9847..9dc1e1f29 100644 --- a/Mastodon/Scene/Discovery/Posts/DiscoveryPostsViewModel+State.swift +++ b/Mastodon/Scene/Discovery/Posts/DiscoveryPostsViewModel+State.swift @@ -136,11 +136,6 @@ extension DiscoveryPostsViewModel.State { default: break } - - guard let authenticationBox = viewModel.context.authenticationService.activeMastodonAuthenticationBox.value else { - stateMachine.enter(Fail.self) - return - } let offset = self.offset let isReloading = offset == nil @@ -148,7 +143,7 @@ extension DiscoveryPostsViewModel.State { 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 diff --git a/Mastodon/Scene/Discovery/Posts/DiscoveryPostsViewModel.swift b/Mastodon/Scene/Discovery/Posts/DiscoveryPostsViewModel.swift index ad6639b16..7a1b044fd 100644 --- a/Mastodon/Scene/Discovery/Posts/DiscoveryPostsViewModel.swift +++ b/Mastodon/Scene/Discovery/Posts/DiscoveryPostsViewModel.swift @@ -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() @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 { diff --git a/Mastodon/Scene/HashtagTimeline/HashtagTimelineViewController.swift b/Mastodon/Scene/HashtagTimeline/HashtagTimelineViewController.swift index b422481f2..a4818aa71 100644 --- a/Mastodon/Scene/HashtagTimeline/HashtagTimelineViewController.swift +++ b/Mastodon/Scene/HashtagTimeline/HashtagTimelineViewController.swift @@ -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 diff --git a/Mastodon/Scene/HashtagTimeline/HashtagTimelineViewModel+Diffable.swift b/Mastodon/Scene/HashtagTimeline/HashtagTimelineViewModel+Diffable.swift index fc80f4846..8d8b0126a 100644 --- a/Mastodon/Scene/HashtagTimeline/HashtagTimelineViewModel+Diffable.swift +++ b/Mastodon/Scene/HashtagTimeline/HashtagTimelineViewModel+Diffable.swift @@ -20,6 +20,7 @@ extension HashtagTimelineViewModel { tableView: tableView, context: context, configuration: StatusSection.Configuration( + authContext: authContext, statusTableViewCellDelegate: statusTableViewCellDelegate, timelineMiddleLoaderTableViewCellDelegate: nil, filterContext: .none, diff --git a/Mastodon/Scene/HashtagTimeline/HashtagTimelineViewModel+State.swift b/Mastodon/Scene/HashtagTimeline/HashtagTimelineViewModel+State.swift index 8e3b6f5d1..57d8c99dd 100644 --- a/Mastodon/Scene/HashtagTimeline/HashtagTimelineViewModel+State.swift +++ b/Mastodon/Scene/HashtagTimeline/HashtagTimelineViewModel+State.swift @@ -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? = { diff --git a/Mastodon/Scene/HashtagTimeline/HashtagTimelineViewModel.swift b/Mastodon/Scene/HashtagTimeline/HashtagTimelineViewModel.swift index 88eb8fcdd..aa57a3930 100644 --- a/Mastodon/Scene/HashtagTimeline/HashtagTimelineViewModel.swift +++ b/Mastodon/Scene/HashtagTimeline/HashtagTimelineViewModel.swift @@ -26,6 +26,7 @@ final class HashtagTimelineViewModel { // input let context: AppContext + let authContext: AuthContext let fetchedResultsController: StatusFetchedResultsController let isFetchingLatestTimeline = CurrentValueSubject(false) let timelinePredicate = CurrentValueSubject(nil) @@ -52,20 +53,16 @@ final class HashtagTimelineViewModel { }() lazy var loadOldestStateMachinePublisher = CurrentValueSubject(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 { diff --git a/Mastodon/Scene/HomeTimeline/HomeTimelineViewController+DebugAction.swift b/Mastodon/Scene/HomeTimeline/HomeTimelineViewController+DebugAction.swift index 2a26c6cf8..d057c0376 100644 --- a/Mastodon/Scene/HomeTimeline/HomeTimelineViewController+DebugAction.swift +++ b/Mastodon/Scene/HomeTimeline/HomeTimelineViewController+DebugAction.swift @@ -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, diff --git a/Mastodon/Scene/HomeTimeline/HomeTimelineViewController.swift b/Mastodon/Scene/HomeTimeline/HomeTimelineViewController.swift index 7be12e8bf..095a362bf 100644 --- a/Mastodon/Scene/HomeTimeline/HomeTimelineViewController.swift +++ b/Mastodon/Scene/HomeTimeline/HomeTimelineViewController.swift @@ -28,7 +28,7 @@ final class HomeTimelineViewController: UIViewController, NeedsDependency, Media weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } } var disposeBag = Set() - 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 diff --git a/Mastodon/Scene/HomeTimeline/HomeTimelineViewModel+Diffable.swift b/Mastodon/Scene/HomeTimeline/HomeTimelineViewModel+Diffable.swift index 92c28242d..9412abad1 100644 --- a/Mastodon/Scene/HomeTimeline/HomeTimelineViewModel+Diffable.swift +++ b/Mastodon/Scene/HomeTimeline/HomeTimelineViewModel+Diffable.swift @@ -21,6 +21,7 @@ extension HomeTimelineViewModel { tableView: tableView, context: context, configuration: StatusSection.Configuration( + authContext: authContext, statusTableViewCellDelegate: statusTableViewCellDelegate, timelineMiddleLoaderTableViewCellDelegate: timelineMiddleLoaderTableViewCellDelegate, filterContext: .home, diff --git a/Mastodon/Scene/HomeTimeline/HomeTimelineViewModel+LoadLatestState.swift b/Mastodon/Scene/HomeTimeline/HomeTimelineViewModel+LoadLatestState.swift index 234504e38..41cea6b58 100644 --- a/Mastodon/Scene/HomeTimeline/HomeTimelineViewModel+LoadLatestState.swift +++ b/Mastodon/Scene/HomeTimeline/HomeTimelineViewModel+LoadLatestState.swift @@ -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) diff --git a/Mastodon/Scene/HomeTimeline/HomeTimelineViewModel+LoadOldestState.swift b/Mastodon/Scene/HomeTimeline/HomeTimelineViewModel+LoadOldestState.swift index 1986ac36a..2a0396f20 100644 --- a/Mastodon/Scene/HomeTimeline/HomeTimelineViewModel+LoadOldestState.swift +++ b/Mastodon/Scene/HomeTimeline/HomeTimelineViewModel+LoadOldestState.swift @@ -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 diff --git a/Mastodon/Scene/HomeTimeline/HomeTimelineViewModel.swift b/Mastodon/Scene/HomeTimeline/HomeTimelineViewModel.swift index 72131f366..4cdd1da55 100644 --- a/Mastodon/Scene/HomeTimeline/HomeTimelineViewModel.swift +++ b/Mastodon/Scene/HomeTimeline/HomeTimelineViewModel.swift @@ -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() - 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( - kind: .home, - acct: .mastodon(domain: authenticationBox.domain, userID: authenticationBox.userID) - ) - } - .store(in: &disposeBag) + fetchedResultsController.predicate = Feed.predicate( + kind: .home, + acct: .mastodon(domain: authContext.mastodonAuthenticationBox.domain, userID: authContext.mastodonAuthenticationBox.userID) + ) 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 { diff --git a/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewController.swift b/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewController.swift index c549d8f99..00e2a9fc8 100644 --- a/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewController.swift +++ b/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewController.swift @@ -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 diff --git a/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewModel+Diffable.swift b/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewModel+Diffable.swift index b32eae76b..cb623ff15 100644 --- a/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewModel+Diffable.swift +++ b/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewModel+Diffable.swift @@ -20,6 +20,7 @@ extension NotificationTimelineViewModel { tableView: tableView, context: context, configuration: NotificationSection.Configuration( + authContext: authContext, notificationTableViewCellDelegate: notificationTableViewCellDelegate, filterContext: .notifications, activeFilters: context.statusFilterService.$activeFilters diff --git a/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewModel+LoadOldestState.swift b/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewModel+LoadOldestState.swift index 5461223cb..346692adc 100644 --- a/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewModel+LoadOldestState.swift +++ b/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewModel+LoadOldestState.swift @@ -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 diff --git a/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewModel.swift b/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewModel.swift index 5e4f7d7ca..f4ce7c2d7 100644 --- a/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewModel.swift +++ b/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewModel.swift @@ -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, - scope: scope - ) - self.feedFetchedResultsController.predicate = predicate - } - .store(in: &disposeBag) + feedFetchedResultsController.predicate = NotificationTimelineViewModel.feedPredicate( + authenticationBox: authContext.mastodonAuthenticationBox, + scope: scope + ) } 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)") diff --git a/Mastodon/Scene/Notification/NotificationViewController.swift b/Mastodon/Scene/Notification/NotificationViewController.swift index 474a778d8..665dffefa 100644 --- a/Mastodon/Scene/Notification/NotificationViewController.swift +++ b/Mastodon/Scene/Notification/NotificationViewController.swift @@ -24,7 +24,7 @@ final class NotificationViewController: TabmanViewController, NeedsDependency { var disposeBag = Set() var observations = Set() - 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 diff --git a/Mastodon/Scene/Notification/NotificationViewModel.swift b/Mastodon/Scene/Notification/NotificationViewModel.swift index 313b206c8..2f2be1805 100644 --- a/Mastodon/Scene/Notification/NotificationViewModel.swift +++ b/Mastodon/Scene/Notification/NotificationViewModel.swift @@ -19,6 +19,7 @@ final class NotificationViewModel { // input let context: AppContext + let authContext: AuthContext let viewDidLoad = PassthroughSubject() // 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 } } diff --git a/Mastodon/Scene/Onboarding/PickServer/MastodonPickServerViewController.swift b/Mastodon/Scene/Onboarding/PickServer/MastodonPickServerViewController.swift index 7845722e5..eb26f75be 100644 --- a/Mastodon/Scene/Onboarding/PickServer/MastodonPickServerViewController.swift +++ b/Mastodon/Scene/Onboarding/PickServer/MastodonPickServerViewController.swift @@ -182,9 +182,13 @@ extension MastodonPickServerViewController { authenticationViewModel .authenticated - .flatMap { [weak self] (domain, user) -> AnyPublisher, 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 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 diff --git a/Mastodon/Scene/Onboarding/Welcome/WelcomeViewController.swift b/Mastodon/Scene/Onboarding/Welcome/WelcomeViewController.swift index 41e77987c..a2b8df83a 100644 --- a/Mastodon/Scene/Onboarding/Welcome/WelcomeViewController.swift +++ b/Mastodon/Scene/Onboarding/Welcome/WelcomeViewController.swift @@ -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 } diff --git a/Mastodon/Scene/Onboarding/Welcome/WelcomeViewModel.swift b/Mastodon/Scene/Onboarding/Welcome/WelcomeViewModel.swift index d57a6ee5f..e835027f3 100644 --- a/Mastodon/Scene/Onboarding/Welcome/WelcomeViewModel.swift +++ b/Mastodon/Scene/Onboarding/Welcome/WelcomeViewModel.swift @@ -17,15 +17,14 @@ final class WelcomeViewModel { let context: AppContext // output - let needsShowDismissEntry = CurrentValueSubject(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) } } diff --git a/Mastodon/Scene/Profile/Bookmark/BookmarkViewController.swift b/Mastodon/Scene/Profile/Bookmark/BookmarkViewController.swift index 6d3d80737..b91b90dad 100644 --- a/Mastodon/Scene/Profile/Bookmark/BookmarkViewController.swift +++ b/Mastodon/Scene/Profile/Bookmark/BookmarkViewController.swift @@ -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 diff --git a/Mastodon/Scene/Profile/Bookmark/BookmarkViewModel+Diffable.swift b/Mastodon/Scene/Profile/Bookmark/BookmarkViewModel+Diffable.swift index 06483012c..69075a8ce 100644 --- a/Mastodon/Scene/Profile/Bookmark/BookmarkViewModel+Diffable.swift +++ b/Mastodon/Scene/Profile/Bookmark/BookmarkViewModel+Diffable.swift @@ -17,6 +17,7 @@ extension BookmarkViewModel { tableView: tableView, context: context, configuration: StatusSection.Configuration( + authContext: authContext, statusTableViewCellDelegate: statusTableViewCellDelegate, timelineMiddleLoaderTableViewCellDelegate: nil, filterContext: .none, diff --git a/Mastodon/Scene/Profile/Bookmark/BookmarkViewModel+State.swift b/Mastodon/Scene/Profile/Bookmark/BookmarkViewModel+State.swift index 085967ec7..21cb8e021 100644 --- a/Mastodon/Scene/Profile/Bookmark/BookmarkViewModel+State.swift +++ b/Mastodon/Scene/Profile/Bookmark/BookmarkViewModel+State.swift @@ -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 diff --git a/Mastodon/Scene/Profile/Bookmark/BookmarkViewModel.swift b/Mastodon/Scene/Profile/Bookmark/BookmarkViewModel.swift index df48b68e7..f56e65526 100644 --- a/Mastodon/Scene/Profile/Bookmark/BookmarkViewModel.swift +++ b/Mastodon/Scene/Profile/Bookmark/BookmarkViewModel.swift @@ -18,7 +18,8 @@ final class BookmarkViewModel { // input let context: AppContext - let activeMastodonAuthenticationBox: CurrentValueSubject + 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) } } diff --git a/Mastodon/Scene/Profile/CachedProfileViewModel.swift b/Mastodon/Scene/Profile/CachedProfileViewModel.swift index f30fac4ea..cdd572fc4 100644 --- a/Mastodon/Scene/Profile/CachedProfileViewModel.swift +++ b/Mastodon/Scene/Profile/CachedProfileViewModel.swift @@ -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)") } diff --git a/Mastodon/Scene/Profile/FamiliarFollowers/FamiliarFollowersViewController.swift b/Mastodon/Scene/Profile/FamiliarFollowers/FamiliarFollowersViewController.swift index c82b8b3ad..b6d6f8313 100644 --- a/Mastodon/Scene/Profile/FamiliarFollowers/FamiliarFollowersViewController.swift +++ b/Mastodon/Scene/Profile/FamiliarFollowers/FamiliarFollowersViewController.swift @@ -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 diff --git a/Mastodon/Scene/Profile/FamiliarFollowers/FamiliarFollowersViewModel.swift b/Mastodon/Scene/Profile/FamiliarFollowers/FamiliarFollowersViewModel.swift index 065ede47c..40d4eb14f 100644 --- a/Mastodon/Scene/Profile/FamiliarFollowers/FamiliarFollowersViewModel.swift +++ b/Mastodon/Scene/Profile/FamiliarFollowers/FamiliarFollowersViewModel.swift @@ -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? - 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 [] } diff --git a/Mastodon/Scene/Profile/Favorite/FavoriteViewController.swift b/Mastodon/Scene/Profile/Favorite/FavoriteViewController.swift index d3cdb2b8e..0b113345e 100644 --- a/Mastodon/Scene/Profile/Favorite/FavoriteViewController.swift +++ b/Mastodon/Scene/Profile/Favorite/FavoriteViewController.swift @@ -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 diff --git a/Mastodon/Scene/Profile/Favorite/FavoriteViewModel+Diffable.swift b/Mastodon/Scene/Profile/Favorite/FavoriteViewModel+Diffable.swift index 58109247e..3723dae5d 100644 --- a/Mastodon/Scene/Profile/Favorite/FavoriteViewModel+Diffable.swift +++ b/Mastodon/Scene/Profile/Favorite/FavoriteViewModel+Diffable.swift @@ -17,6 +17,7 @@ extension FavoriteViewModel { tableView: tableView, context: context, configuration: StatusSection.Configuration( + authContext: authContext, statusTableViewCellDelegate: statusTableViewCellDelegate, timelineMiddleLoaderTableViewCellDelegate: nil, filterContext: .none, diff --git a/Mastodon/Scene/Profile/Favorite/FavoriteViewModel+State.swift b/Mastodon/Scene/Profile/Favorite/FavoriteViewModel+State.swift index 84f2da1db..b583ec139 100644 --- a/Mastodon/Scene/Profile/Favorite/FavoriteViewModel+State.swift +++ b/Mastodon/Scene/Profile/Favorite/FavoriteViewModel+State.swift @@ -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 diff --git a/Mastodon/Scene/Profile/Favorite/FavoriteViewModel.swift b/Mastodon/Scene/Profile/Favorite/FavoriteViewModel.swift index 0570df2e9..0dd3c7203 100644 --- a/Mastodon/Scene/Profile/Favorite/FavoriteViewModel.swift +++ b/Mastodon/Scene/Profile/Favorite/FavoriteViewModel.swift @@ -18,7 +18,7 @@ final class FavoriteViewModel { // input let context: AppContext - let activeMastodonAuthenticationBox: CurrentValueSubject + 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) } } diff --git a/Mastodon/Scene/Profile/Follower/FollowerListViewController.swift b/Mastodon/Scene/Profile/Follower/FollowerListViewController.swift index ff2977d45..57e068c72 100644 --- a/Mastodon/Scene/Profile/Follower/FollowerListViewController.swift +++ b/Mastodon/Scene/Profile/Follower/FollowerListViewController.swift @@ -82,15 +82,15 @@ 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 - guard let self = self else { return } - self.viewModel.stateMachine.enter(FollowerListViewModel.State.Reloading.self) - } - .store(in: &disposeBag) + .receive(on: DispatchQueue.main) + .sink { [weak self] _ in + guard let self = self else { return } + self.viewModel.stateMachine.enter(FollowerListViewModel.State.Reloading.self) + } + .store(in: &disposeBag) } override func viewWillAppear(_ animated: Bool) { @@ -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 diff --git a/Mastodon/Scene/Profile/Follower/FollowerListViewModel+Diffable.swift b/Mastodon/Scene/Profile/Follower/FollowerListViewModel+Diffable.swift index 15cc1be13..7a30c3234 100644 --- a/Mastodon/Scene/Profile/Follower/FollowerListViewModel+Diffable.swift +++ b/Mastodon/Scene/Profile/Follower/FollowerListViewModel+Diffable.swift @@ -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: diff --git a/Mastodon/Scene/Profile/Follower/FollowerListViewModel+State.swift b/Mastodon/Scene/Profile/Follower/FollowerListViewModel+State.swift index ffedeae46..fe336f39f 100644 --- a/Mastodon/Scene/Profile/Follower/FollowerListViewModel+State.swift +++ b/Mastodon/Scene/Profile/Follower/FollowerListViewModel+State.swift @@ -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") diff --git a/Mastodon/Scene/Profile/Follower/FollowerListViewModel.swift b/Mastodon/Scene/Profile/Follower/FollowerListViewModel.swift index af0f6b139..1b0d505b5 100644 --- a/Mastodon/Scene/Profile/Follower/FollowerListViewModel.swift +++ b/Mastodon/Scene/Profile/Follower/FollowerListViewModel.swift @@ -20,11 +20,13 @@ final class FollowerListViewModel { // input let context: AppContext - let domain: CurrentValueSubject - let userID: CurrentValueSubject + let authContext: AuthContext let userFetchedResultsController: UserFetchedResultsController let listBatchFetchViewModel = ListBatchFetchViewModel() + @Published var domain: String? + @Published var userID: String? + // output var diffableDataSource: UITableViewDiffableDataSource? 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 } } diff --git a/Mastodon/Scene/Profile/Following/FollowingListViewController.swift b/Mastodon/Scene/Profile/Following/FollowingListViewController.swift index 18994d26b..ccf0f1819 100644 --- a/Mastodon/Scene/Profile/Following/FollowingListViewController.swift +++ b/Mastodon/Scene/Profile/Following/FollowingListViewController.swift @@ -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 diff --git a/Mastodon/Scene/Profile/Following/FollowingListViewModel+Diffable.swift b/Mastodon/Scene/Profile/Following/FollowingListViewModel+Diffable.swift index 116e7567c..e022c5736 100644 --- a/Mastodon/Scene/Profile/Following/FollowingListViewModel+Diffable.swift +++ b/Mastodon/Scene/Profile/Following/FollowingListViewModel+Diffable.swift @@ -7,6 +7,7 @@ import UIKit import MastodonAsset +import MastodonCore import MastodonLocalization extension FollowingListViewModel { @@ -44,23 +45,23 @@ extension FollowingListViewModel { snapshot.appendSections([.main]) let items = records.map { UserItem.user(record: $0) } snapshot.appendItems(items, toSection: .main) - + if let currentState = self.stateMachine.currentState { switch currentState { 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: break } } - + diffableDataSource.apply(snapshot, animatingDifferences: false) } .store(in: &disposeBag) diff --git a/Mastodon/Scene/Profile/Following/FollowingListViewModel+State.swift b/Mastodon/Scene/Profile/Following/FollowingListViewModel+State.swift index c01a9c8c6..d8c1f765a 100644 --- a/Mastodon/Scene/Profile/Following/FollowingListViewModel+State.swift +++ b/Mastodon/Scene/Profile/Following/FollowingListViewModel+State.swift @@ -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)") diff --git a/Mastodon/Scene/Profile/Following/FollowingListViewModel.swift b/Mastodon/Scene/Profile/Following/FollowingListViewModel.swift index a809e14d0..12b294b8b 100644 --- a/Mastodon/Scene/Profile/Following/FollowingListViewModel.swift +++ b/Mastodon/Scene/Profile/Following/FollowingListViewModel.swift @@ -19,11 +19,13 @@ final class FollowingListViewModel { // input let context: AppContext - let domain: CurrentValueSubject - let userID: CurrentValueSubject + let authContext: AuthContext let userFetchedResultsController: UserFetchedResultsController let listBatchFetchViewModel = ListBatchFetchViewModel() + @Published var domain: String? + @Published var userID: String? + // output var diffableDataSource: UITableViewDiffableDataSource? 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() } diff --git a/Mastodon/Scene/Profile/Header/ProfileHeaderViewController.swift b/Mastodon/Scene/Profile/Header/ProfileHeaderViewController.swift index 5c7ce808a..c5dbbecd4 100644 --- a/Mastodon/Scene/Profile/Header/ProfileHeaderViewController.swift +++ b/Mastodon/Scene/Profile/Header/ProfileHeaderViewController.swift @@ -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 diff --git a/Mastodon/Scene/Profile/Header/ProfileHeaderViewModel.swift b/Mastodon/Scene/Profile/Header/ProfileHeaderViewModel.swift index 15a13de84..65f15efa7 100644 --- a/Mastodon/Scene/Profile/Header/ProfileHeaderViewModel.swift +++ b/Mastodon/Scene/Profile/Header/ProfileHeaderViewModel.swift @@ -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) diff --git a/Mastodon/Scene/Profile/MeProfileViewModel.swift b/Mastodon/Scene/Profile/MeProfileViewModel.swift index d6a30fd04..995e32002 100644 --- a/Mastodon/Scene/Profile/MeProfileViewModel.swift +++ b/Mastodon/Scene/Profile/MeProfileViewModel.swift @@ -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 diff --git a/Mastodon/Scene/Profile/ProfileViewController.swift b/Mastodon/Scene/Profile/ProfileViewController.swift index 3393a2edf..30c37473e 100644 --- a/Mastodon/Scene/Profile/ProfileViewController.swift +++ b/Mastodon/Scene/Profile/ProfileViewController.swift @@ -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(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 = .init(objectID: user.objectID) @@ -866,8 +863,7 @@ extension ProfileViewController: MastodonMenuDelegate { status: nil, button: nil, barButtonItem: self.moreMenuBarButtonItem - ), - authenticationBox: authenticationBox + ) ) } // end Task } diff --git a/Mastodon/Scene/Profile/ProfileViewModel.swift b/Mastodon/Scene/Profile/ProfileViewModel.swift index 3893c80a1..e23b465d4 100644 --- a/Mastodon/Scene/Profile/ProfileViewModel.swift +++ b/Mastodon/Scene/Profile/ProfileViewModel.swift @@ -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(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(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 { - 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 diff --git a/Mastodon/Scene/Profile/RemoteProfileViewModel.swift b/Mastodon/Scene/Profile/RemoteProfileViewModel.swift index cb1f0e4c3..1e1388c95 100644 --- a/Mastodon/Scene/Profile/RemoteProfileViewModel.swift +++ b/Mastodon/Scene/Profile/RemoteProfileViewModel.swift @@ -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 } diff --git a/Mastodon/Scene/Profile/Timeline/UserTimelineViewController.swift b/Mastodon/Scene/Profile/Timeline/UserTimelineViewController.swift index beed25086..8a983da33 100644 --- a/Mastodon/Scene/Profile/Timeline/UserTimelineViewController.swift +++ b/Mastodon/Scene/Profile/Timeline/UserTimelineViewController.swift @@ -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 diff --git a/Mastodon/Scene/Profile/Timeline/UserTimelineViewModel+Diffable.swift b/Mastodon/Scene/Profile/Timeline/UserTimelineViewModel+Diffable.swift index 7f7341aa6..863d7b44e 100644 --- a/Mastodon/Scene/Profile/Timeline/UserTimelineViewModel+Diffable.swift +++ b/Mastodon/Scene/Profile/Timeline/UserTimelineViewModel+Diffable.swift @@ -18,6 +18,7 @@ extension UserTimelineViewModel { tableView: tableView, context: context, configuration: StatusSection.Configuration( + authContext: authContext, statusTableViewCellDelegate: statusTableViewCellDelegate, timelineMiddleLoaderTableViewCellDelegate: nil, filterContext: .none, diff --git a/Mastodon/Scene/Profile/Timeline/UserTimelineViewModel+State.swift b/Mastodon/Scene/Profile/Timeline/UserTimelineViewModel+State.swift index 17409a2bc..b87d4305e 100644 --- a/Mastodon/Scene/Profile/Timeline/UserTimelineViewModel+State.swift +++ b/Mastodon/Scene/Profile/Timeline/UserTimelineViewModel+State.swift @@ -138,10 +138,6 @@ extension UserTimelineViewModel.State { return } - guard let authenticationBox = viewModel.context.authenticationService.activeMastodonAuthenticationBox.value else { - stateMachine.enter(Fail.self) - return - } let queryFilter = viewModel.queryFilter Task { @@ -154,7 +150,7 @@ extension UserTimelineViewModel.State { excludeReplies: queryFilter.excludeReplies, excludeReblogs: queryFilter.excludeReblogs, onlyMedia: queryFilter.onlyMedia, - authenticationBox: authenticationBox + authenticationBox: viewModel.authContext.mastodonAuthenticationBox ) var hasNewStatusesAppend = false diff --git a/Mastodon/Scene/Profile/Timeline/UserTimelineViewModel.swift b/Mastodon/Scene/Profile/Timeline/UserTimelineViewModel.swift index bd28d2c79..0d85b6807 100644 --- a/Mastodon/Scene/Profile/Timeline/UserTimelineViewModel.swift +++ b/Mastodon/Scene/Profile/Timeline/UserTimelineViewModel.swift @@ -20,6 +20,7 @@ final class UserTimelineViewModel { // input let context: AppContext + let authContext: AuthContext let title: String let statusFetchedResultsController: StatusFetchedResultsController let listBatchFetchViewModel = ListBatchFetchViewModel() @@ -50,23 +51,19 @@ final class UserTimelineViewModel { init( context: AppContext, + authContext: AuthContext, title: String, queryFilter: QueryFilter ) { self.context = context + self.authContext = authContext self.title = title self.statusFetchedResultsController = StatusFetchedResultsController( managedObjectContext: context.managedObjectContext, - domain: nil, + domain: authContext.mastodonAuthenticationBox.domain, additionalTweetPredicate: nil ) self.queryFilter = queryFilter - // super.init() - - context.authenticationService.activeMastodonAuthenticationBox - .map { $0?.domain } - .assign(to: \.domain, on: statusFetchedResultsController) - .store(in: &disposeBag) } deinit { diff --git a/Mastodon/Scene/Profile/UserLIst/FavoritedBy/FavoritedByViewController.swift b/Mastodon/Scene/Profile/UserLIst/FavoritedBy/FavoritedByViewController.swift index 5d9a20610..ebce374e7 100644 --- a/Mastodon/Scene/Profile/UserLIst/FavoritedBy/FavoritedByViewController.swift +++ b/Mastodon/Scene/Profile/UserLIst/FavoritedBy/FavoritedByViewController.swift @@ -93,6 +93,11 @@ extension FavoritedByViewController { } +// MARK: - AuthContextProvider +extension FavoritedByViewController: AuthContextProvider { + var authContext: AuthContext { viewModel.authContext } +} + // MARK: - UITableViewDelegate extension FavoritedByViewController: UITableViewDelegate, AutoGenerateTableViewDelegate { // sourcery:inline:FavoritedByViewController.AutoGenerateTableViewDelegate diff --git a/Mastodon/Scene/Profile/UserLIst/RebloggedBy/RebloggedByViewController.swift b/Mastodon/Scene/Profile/UserLIst/RebloggedBy/RebloggedByViewController.swift index 3f3e239a1..0688bcccb 100644 --- a/Mastodon/Scene/Profile/UserLIst/RebloggedBy/RebloggedByViewController.swift +++ b/Mastodon/Scene/Profile/UserLIst/RebloggedBy/RebloggedByViewController.swift @@ -93,6 +93,11 @@ extension RebloggedByViewController { } +// MARK: - AuthContextProvider +extension RebloggedByViewController: AuthContextProvider { + var authContext: AuthContext { viewModel.authContext } +} + // MARK: - UITableViewDelegate extension RebloggedByViewController: UITableViewDelegate, AutoGenerateTableViewDelegate { // sourcery:inline:RebloggedByViewController.AutoGenerateTableViewDelegate diff --git a/Mastodon/Scene/Profile/UserLIst/UserListViewModel+State.swift b/Mastodon/Scene/Profile/UserLIst/UserListViewModel+State.swift index 9098c7f81..18c37c403 100644 --- a/Mastodon/Scene/Profile/UserLIst/UserListViewModel+State.swift +++ b/Mastodon/Scene/Profile/UserLIst/UserListViewModel+State.swift @@ -137,10 +137,6 @@ extension UserListViewModel.State { } guard let viewModel = viewModel, let stateMachine = stateMachine else { return } - guard let authenticationBox = viewModel.context.authenticationService.activeMastodonAuthenticationBox.value else { - stateMachine.enter(Fail.self) - return - } let maxID = self.maxID @@ -152,13 +148,13 @@ extension UserListViewModel.State { response = try await viewModel.context.apiService.favoritedBy( status: status, query: .init(maxID: maxID, limit: nil), - authenticationBox: authenticationBox + authenticationBox: viewModel.authContext.mastodonAuthenticationBox ) case .rebloggedBy(let status): response = try await viewModel.context.apiService.rebloggedBy( status: status, query: .init(maxID: maxID, limit: nil), - authenticationBox: authenticationBox + authenticationBox: viewModel.authContext.mastodonAuthenticationBox ) } diff --git a/Mastodon/Scene/Profile/UserLIst/UserListViewModel.swift b/Mastodon/Scene/Profile/UserLIst/UserListViewModel.swift index f8d9a3bd6..2a0ed0271 100644 --- a/Mastodon/Scene/Profile/UserLIst/UserListViewModel.swift +++ b/Mastodon/Scene/Profile/UserLIst/UserListViewModel.swift @@ -19,6 +19,7 @@ final class UserListViewModel { // input let context: AppContext + let authContext: AuthContext let kind: Kind let userFetchedResultsController: UserFetchedResultsController let listBatchFetchViewModel = ListBatchFetchViewModel() @@ -39,21 +40,18 @@ final class UserListViewModel { public init( context: AppContext, + authContext: AuthContext, kind: Kind ) { self.context = context + self.authContext = authContext self.kind = kind 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) } } diff --git a/Mastodon/Scene/Report/Report/ReportViewController.swift b/Mastodon/Scene/Report/Report/ReportViewController.swift index 57107be47..f1418c5a1 100644 --- a/Mastodon/Scene/Report/Report/ReportViewController.swift +++ b/Mastodon/Scene/Report/Report/ReportViewController.swift @@ -94,22 +94,23 @@ extension ReportViewController: ReportReasonViewControllerDelegate { case .dislike: let reportResultViewModel = ReportResultViewModel( context: context, + authContext: viewModel.authContext, user: viewModel.user, isReported: false ) - coordinator.present( + _ = coordinator.present( scene: .reportResult(viewModel: reportResultViewModel), from: self, transition: .show ) case .violateRule: - coordinator.present( + _ = coordinator.present( scene: .reportServerRules(viewModel: viewModel.reportServerRulesViewModel), from: self, transition: .show ) case .spam, .other: - coordinator.present( + _ = coordinator.present( scene: .reportStatus(viewModel: viewModel.reportStatusViewModel), from: self, transition: .show @@ -144,7 +145,7 @@ extension ReportViewController: ReportStatusViewControllerDelegate { } private func coordinateToReportSupplementary() { - coordinator.present( + _ = coordinator.present( scene: .reportSupplementary(viewModel: viewModel.reportSupplementaryViewModel), from: self, transition: .show @@ -170,11 +171,12 @@ extension ReportViewController: ReportSupplementaryViewControllerDelegate { let reportResultViewModel = ReportResultViewModel( context: context, + authContext: viewModel.authContext, user: viewModel.user, isReported: true ) - coordinator.present( + _ = coordinator.present( scene: .reportResult(viewModel: reportResultViewModel), from: self, transition: .show @@ -184,7 +186,7 @@ extension ReportViewController: ReportSupplementaryViewControllerDelegate { let alertController = UIAlertController(for: error, title: nil, preferredStyle: .alert) let okAction = UIAlertAction(title: L10n.Common.Controls.Actions.ok, style: .default, handler: nil) alertController.addAction(okAction) - self.coordinator.present( + _ = self.coordinator.present( scene: .alertController(alertController: alertController), from: nil, transition: .alertController(animated: true, completion: nil) diff --git a/Mastodon/Scene/Report/Report/ReportViewModel.swift b/Mastodon/Scene/Report/Report/ReportViewModel.swift index 4e59cb440..c368ce42c 100644 --- a/Mastodon/Scene/Report/Report/ReportViewModel.swift +++ b/Mastodon/Scene/Report/Report/ReportViewModel.swift @@ -28,6 +28,7 @@ class ReportViewModel { // input let context: AppContext + let authContext: AuthContext let user: ManagedObjectRecord let status: ManagedObjectRecord? @@ -37,22 +38,20 @@ class ReportViewModel { init( context: AppContext, + authContext: AuthContext, user: ManagedObjectRecord, status: ManagedObjectRecord? ) { self.context = context + self.authContext = authContext self.user = user self.status = status self.reportReasonViewModel = ReportReasonViewModel(context: context) self.reportServerRulesViewModel = ReportServerRulesViewModel(context: context) - self.reportStatusViewModel = ReportStatusViewModel(context: context, user: user, status: status) - self.reportSupplementaryViewModel = ReportSupplementaryViewModel(context: context, user: user) + self.reportStatusViewModel = ReportStatusViewModel(context: context, authContext: authContext, user: user, status: status) + self.reportSupplementaryViewModel = ReportSupplementaryViewModel(context: context, authContext: authContext, user: user) // end init - guard let authenticationBox = context.authenticationService.activeMastodonAuthenticationBox.value else { - return - } - // setup reason viewModel if status != nil { reportReasonViewModel.headline = L10n.Scene.Report.StepOne.whatsWrongWithThisPost @@ -74,7 +73,7 @@ class ReportViewModel { // bind server rules Task { @MainActor in do { - let response = try await context.apiService.instance(domain: authenticationBox.domain) + let response = try await context.apiService.instance(domain: authContext.mastodonAuthenticationBox.domain) .timeout(3, scheduler: DispatchQueue.main) .singleOutput() let rules = response.value.rules ?? [] @@ -95,12 +94,7 @@ class ReportViewModel { extension ReportViewModel { @MainActor func report() async throws { - guard let authenticationBox = context.authenticationService.activeMastodonAuthenticationBox.value, - !isReporting - else { - assertionFailure() - return - } + guard !isReporting else { return } let managedObjectContext = context.managedObjectContext let _query: Mastodon.API.Reports.FileReportQuery? = try await managedObjectContext.perform { diff --git a/Mastodon/Scene/Report/ReportResult/ReportResultView.swift b/Mastodon/Scene/Report/ReportResult/ReportResultView.swift index b1ad76415..75021934b 100644 --- a/Mastodon/Scene/Report/ReportResult/ReportResultView.swift +++ b/Mastodon/Scene/Report/ReportResult/ReportResultView.swift @@ -156,65 +156,69 @@ struct ReportActionButton: View { } -#if DEBUG -struct ReportResultView_Previews: PreviewProvider { - - static func viewModel(isReported: Bool) -> ReportResultViewModel { - let context = AppContext.shared - let request = MastodonUser.sortedFetchRequest - request.fetchLimit = 1 - - let property = MastodonUser.Property( - identifier: "1", - domain: "domain.com", - id: "1", - acct: "@user@domain.com", - username: "user", - displayName: "User", - avatar: "", - avatarStatic: "", - header: "", - headerStatic: "", - note: "", - url: "", - statusesCount: Int64(100), - followingCount: Int64(100), - followersCount: Int64(100), - locked: false, - bot: false, - suspended: false, - createdAt: Date(), - updatedAt: Date(), - emojis: [], - fields: [] - ) - let user = try! context.managedObjectContext.fetch(request).first ?? MastodonUser.insert(into: context.managedObjectContext, property: property) - - return ReportResultViewModel( - context: context, - user: .init(objectID: user.objectID), - isReported: isReported - ) - } - static var previews: some View { - Group { - NavigationView { - ReportResultView(viewModel: viewModel(isReported: true)) - .navigationBarTitle(Text("")) - .navigationBarTitleDisplayMode(.inline) - } - NavigationView { - ReportResultView(viewModel: viewModel(isReported: false)) - .navigationBarTitle(Text("")) - .navigationBarTitleDisplayMode(.inline) - } - NavigationView { - ReportResultView(viewModel: viewModel(isReported: true)) - .navigationBarTitle(Text("")) - .navigationBarTitleDisplayMode(.inline) - } - .preferredColorScheme(.dark) - } - } -} -#endif +//#if DEBUG +// +//struct ReportResultView_Previews: PreviewProvider { +// +// static func viewModel(isReported: Bool) -> ReportResultViewModel { +// let context = AppContext.shared +// let request = MastodonUser.sortedFetchRequest +// request.fetchLimit = 1 +// +// let property = MastodonUser.Property( +// identifier: "1", +// domain: "domain.com", +// id: "1", +// acct: "@user@domain.com", +// username: "user", +// displayName: "User", +// avatar: "", +// avatarStatic: "", +// header: "", +// headerStatic: "", +// note: "", +// url: "", +// statusesCount: Int64(100), +// followingCount: Int64(100), +// followersCount: Int64(100), +// locked: false, +// bot: false, +// suspended: false, +// createdAt: Date(), +// updatedAt: Date(), +// emojis: [], +// fields: [] +// ) +// let user = try! context.managedObjectContext.fetch(request).first ?? MastodonUser.insert(into: context.managedObjectContext, property: property) +// +// return ReportResultViewModel( +// context: context, +// authContext: nil, +// user: .init(objectID: user.objectID), +// isReported: isReported +// ) +// } +// static var previews: some View { +// Group { +// NavigationView { +// ReportResultView(viewModel: viewModel(isReported: true)) +// .navigationBarTitle(Text("")) +// .navigationBarTitleDisplayMode(.inline) +// } +// NavigationView { +// ReportResultView(viewModel: viewModel(isReported: false)) +// .navigationBarTitle(Text("")) +// .navigationBarTitleDisplayMode(.inline) +// } +// NavigationView { +// ReportResultView(viewModel: viewModel(isReported: true)) +// .navigationBarTitle(Text("")) +// .navigationBarTitleDisplayMode(.inline) +// } +// .preferredColorScheme(.dark) +// } +// } +// +//} +// +//#endif diff --git a/Mastodon/Scene/Report/ReportResult/ReportResultViewController.swift b/Mastodon/Scene/Report/ReportResult/ReportResultViewController.swift index 1a5aabb67..10dcdf373 100644 --- a/Mastodon/Scene/Report/ReportResult/ReportResultViewController.swift +++ b/Mastodon/Scene/Report/ReportResult/ReportResultViewController.swift @@ -93,17 +93,13 @@ extension ReportResultViewController { .throttle(for: 0.3, scheduler: DispatchQueue.main, latest: false) .sink { [weak self] in guard let self = self else { return } - guard let authenticationBox = self.context.authenticationService.activeMastodonAuthenticationBox.value else { - return - } Task { @MainActor in guard !self.viewModel.isRequestFollow else { return } self.viewModel.isRequestFollow = true do { try await DataSourceFacade.responseToUserFollowAction( dependency: self, - user: self.viewModel.user, - authenticationBox: authenticationBox + user: self.viewModel.user ) } catch { // handle error @@ -117,17 +113,13 @@ extension ReportResultViewController { .throttle(for: 0.3, scheduler: DispatchQueue.main, latest: false) .sink { [weak self] in guard let self = self else { return } - guard let authenticationBox = self.context.authenticationService.activeMastodonAuthenticationBox.value else { - return - } Task { @MainActor in guard !self.viewModel.isRequestMute else { return } self.viewModel.isRequestMute = true do { try await DataSourceFacade.responseToUserMuteAction( dependency: self, - user: self.viewModel.user, - authenticationBox: authenticationBox + user: self.viewModel.user ) } catch { // handle error @@ -141,17 +133,13 @@ extension ReportResultViewController { .throttle(for: 0.3, scheduler: DispatchQueue.main, latest: false) .sink { [weak self] in guard let self = self else { return } - guard let authenticationBox = self.context.authenticationService.activeMastodonAuthenticationBox.value else { - return - } Task { @MainActor in guard !self.viewModel.isRequestBlock else { return } self.viewModel.isRequestBlock = true do { try await DataSourceFacade.responseToUserBlockAction( dependency: self, - user: self.viewModel.user, - authenticationBox: authenticationBox + user: self.viewModel.user ) } catch { // handle error @@ -176,6 +164,11 @@ extension ReportResultViewController { } +// MARK: - AuthContextProvider +extension ReportResultViewController: AuthContextProvider { + var authContext: AuthContext { viewModel.authContext } +} + // MARK: - PanPopableViewController extension ReportResultViewController: PanPopableViewController { var isPanPopable: Bool { false } diff --git a/Mastodon/Scene/Report/ReportResult/ReportResultViewModel.swift b/Mastodon/Scene/Report/ReportResult/ReportResultViewModel.swift index 8508d1596..8123a8773 100644 --- a/Mastodon/Scene/Report/ReportResult/ReportResultViewModel.swift +++ b/Mastodon/Scene/Report/ReportResult/ReportResultViewModel.swift @@ -23,6 +23,7 @@ class ReportResultViewModel: ObservableObject { // input let context: AppContext + let authContext: AuthContext let user: ManagedObjectRecord let isReported: Bool @@ -47,17 +48,19 @@ class ReportResultViewModel: ObservableObject { init( context: AppContext, + authContext: AuthContext, user: ManagedObjectRecord, isReported: Bool ) { self.context = context + self.authContext = authContext self.user = user self.isReported = isReported // end init Task { @MainActor in guard let user = user.object(in: context.managedObjectContext) else { return } - guard let me = context.authenticationService.activeMastodonAuthenticationBox.value?.authenticationRecord.object(in: context.managedObjectContext)?.user else { return } + guard let me = authContext.mastodonAuthenticationBox.authenticationRecord.object(in: context.managedObjectContext)?.user else { return } self.relationshipViewModel.user = user self.relationshipViewModel.me = me diff --git a/Mastodon/Scene/Report/ReportStatus/ReportStatusViewModel+Diffable.swift b/Mastodon/Scene/Report/ReportStatus/ReportStatusViewModel+Diffable.swift index 4610a38d3..9879863d6 100644 --- a/Mastodon/Scene/Report/ReportStatus/ReportStatusViewModel+Diffable.swift +++ b/Mastodon/Scene/Report/ReportStatus/ReportStatusViewModel+Diffable.swift @@ -25,7 +25,7 @@ extension ReportStatusViewModel { diffableDataSource = ReportSection.diffableDataSource( tableView: tableView, context: context, - configuration: ReportSection.Configuration() + configuration: ReportSection.Configuration(authContext: authContext) ) var snapshot = NSDiffableDataSourceSnapshot() diff --git a/Mastodon/Scene/Report/ReportStatus/ReportStatusViewModel+State.swift b/Mastodon/Scene/Report/ReportStatus/ReportStatusViewModel+State.swift index 6e9d48af0..01e8715d1 100644 --- a/Mastodon/Scene/Report/ReportStatus/ReportStatusViewModel+State.swift +++ b/Mastodon/Scene/Report/ReportStatus/ReportStatusViewModel+State.swift @@ -76,10 +76,6 @@ extension ReportStatusViewModel.State { override func didEnter(from previousState: GKState?) { super.didEnter(from: previousState) guard let viewModel = viewModel, let stateMachine = stateMachine else { return } - guard let authenticationBox = viewModel.context.authenticationService.activeMastodonAuthenticationBox.value else { - stateMachine.enter(Fail.self) - return - } let maxID = viewModel.statusFetchedResultsController.statusIDs.last @@ -102,7 +98,7 @@ extension ReportStatusViewModel.State { excludeReplies: true, excludeReblogs: true, onlyMedia: false, - authenticationBox: authenticationBox + authenticationBox: viewModel.authContext.mastodonAuthenticationBox ) var hasNewStatusesAppend = false diff --git a/Mastodon/Scene/Report/ReportStatus/ReportStatusViewModel.swift b/Mastodon/Scene/Report/ReportStatus/ReportStatusViewModel.swift index b539909da..5b80a9f3a 100644 --- a/Mastodon/Scene/Report/ReportStatus/ReportStatusViewModel.swift +++ b/Mastodon/Scene/Report/ReportStatus/ReportStatusViewModel.swift @@ -24,6 +24,7 @@ class ReportStatusViewModel { // input let context: AppContext + let authContext: AuthContext let user: ManagedObjectRecord let status: ManagedObjectRecord? let statusFetchedResultsController: StatusFetchedResultsController @@ -50,15 +51,17 @@ class ReportStatusViewModel { init( context: AppContext, + authContext: AuthContext, user: ManagedObjectRecord, status: ManagedObjectRecord? ) { self.context = context + self.authContext = authContext self.user = user self.status = status self.statusFetchedResultsController = StatusFetchedResultsController( managedObjectContext: context.managedObjectContext, - domain: nil, + domain: authContext.mastodonAuthenticationBox.domain, additionalTweetPredicate: nil ) // end init @@ -66,12 +69,7 @@ class ReportStatusViewModel { if let status = status { selectStatuses.append(status) } - - context.authenticationService.activeMastodonAuthenticationBox - .map { $0?.domain } - .assign(to: \.domain, on: statusFetchedResultsController) - .store(in: &disposeBag) - + $selectStatuses .map { statuses -> Bool in return status == nil ? !statuses.isEmpty : statuses.count > 1 diff --git a/Mastodon/Scene/Report/ReportSupplementary/ReportSupplementaryViewModel+Diffable.swift b/Mastodon/Scene/Report/ReportSupplementary/ReportSupplementaryViewModel+Diffable.swift index 8cbc16242..099f542b7 100644 --- a/Mastodon/Scene/Report/ReportSupplementary/ReportSupplementaryViewModel+Diffable.swift +++ b/Mastodon/Scene/Report/ReportSupplementary/ReportSupplementaryViewModel+Diffable.swift @@ -25,7 +25,7 @@ extension ReportSupplementaryViewModel { diffableDataSource = ReportSection.diffableDataSource( tableView: tableView, context: context, - configuration: ReportSection.Configuration() + configuration: ReportSection.Configuration(authContext: authContext) ) var snapshot = NSDiffableDataSourceSnapshot() diff --git a/Mastodon/Scene/Report/ReportSupplementary/ReportSupplementaryViewModel.swift b/Mastodon/Scene/Report/ReportSupplementary/ReportSupplementaryViewModel.swift index 8ddc2d91a..a4239bbc4 100644 --- a/Mastodon/Scene/Report/ReportSupplementary/ReportSupplementaryViewModel.swift +++ b/Mastodon/Scene/Report/ReportSupplementary/ReportSupplementaryViewModel.swift @@ -16,7 +16,8 @@ class ReportSupplementaryViewModel { weak var delegate: ReportSupplementaryViewControllerDelegate? // Input - var context: AppContext + let context: AppContext + let authContext: AuthContext let user: ManagedObjectRecord let commentContext = ReportItem.CommentContext() @@ -29,9 +30,11 @@ class ReportSupplementaryViewModel { init( context: AppContext, + authContext: AuthContext, user: ManagedObjectRecord ) { self.context = context + self.authContext = authContext self.user = user // end init diff --git a/Mastodon/Scene/Root/ContentSplitViewController.swift b/Mastodon/Scene/Root/ContentSplitViewController.swift index 503c56de5..3f4758e8e 100644 --- a/Mastodon/Scene/Root/ContentSplitViewController.swift +++ b/Mastodon/Scene/Root/ContentSplitViewController.swift @@ -32,7 +32,7 @@ final class ContentSplitViewController: UIViewController, NeedsDependency { let sidebarViewController = SidebarViewController() sidebarViewController.context = context sidebarViewController.coordinator = coordinator - sidebarViewController.viewModel = SidebarViewModel(context: context) + sidebarViewController.viewModel = SidebarViewModel(context: context, authContext: authContext) sidebarViewController.delegate = self return sidebarViewController }() @@ -111,8 +111,14 @@ extension ContentSplitViewController: SidebarViewControllerDelegate { func sidebarViewController(_ sidebarViewController: SidebarViewController, didLongPressItem item: SidebarViewModel.Item, sourceView: UIView) { guard case let .tab(tab) = item, tab == .me else { return } + guard let authContext = authContext else { return } - let accountListViewController = coordinator.present(scene: .accountList, from: nil, transition: .popover(sourceView: sourceView)) as! AccountListViewController + let accountListViewModel = AccountListViewModel(context: context, authContext: authContext) + let accountListViewController = coordinator.present( + scene: .accountList(viewModel: accountListViewModel), + from: nil, + transition: .popover(sourceView: sourceView) + ) as! AccountListViewController accountListViewController.dragIndicatorView.barView.isHidden = true // content width needs > 300 to make checkmark display accountListViewController.preferredContentSize = CGSize(width: 375, height: 400) diff --git a/Mastodon/Scene/Root/MainTab/MainTabBarController.swift b/Mastodon/Scene/Root/MainTab/MainTabBarController.swift index 5c910390c..31d7d9fde 100644 --- a/Mastodon/Scene/Root/MainTab/MainTabBarController.swift +++ b/Mastodon/Scene/Root/MainTab/MainTabBarController.swift @@ -105,18 +105,24 @@ class MainTabBarController: UITabBarController { } } - func viewController(context: AppContext, coordinator: SceneCoordinator) -> UIViewController { + func viewController(context: AppContext, authContext: AuthContext?, coordinator: SceneCoordinator) -> UIViewController { + guard let authContext = authContext else { + return UITableViewController() + } + let viewController: UIViewController switch self { case .home: let _viewController = HomeTimelineViewController() _viewController.context = context _viewController.coordinator = coordinator + _viewController.viewModel = .init(context: context, authContext: authContext) viewController = _viewController case .search: let _viewController = SearchViewController() _viewController.context = context _viewController.coordinator = coordinator + _viewController.viewModel = .init(context: context, authContext: authContext) viewController = _viewController case .compose: viewController = UIViewController() @@ -124,12 +130,13 @@ class MainTabBarController: UITabBarController { let _viewController = NotificationViewController() _viewController.context = context _viewController.coordinator = coordinator + _viewController.viewModel = .init(context: context, authContext: authContext) viewController = _viewController case .me: let _viewController = ProfileViewController() _viewController.context = context _viewController.coordinator = coordinator - _viewController.viewModel = MeProfileViewModel(context: context) + _viewController.viewModel = MeProfileViewModel(context: context, authContext: authContext) viewController = _viewController } viewController.title = self.title @@ -185,7 +192,7 @@ extension MainTabBarController { // seealso: `ThemeService.apply(theme:)` let tabs = Tab.allCases let viewControllers: [UIViewController] = tabs.map { tab in - let viewController = tab.viewController(context: context, coordinator: coordinator) + let viewController = tab.viewController(context: context, authContext: authContext, coordinator: coordinator) viewController.tabBarItem.tag = tab.tag viewController.tabBarItem.title = tab.title // needs for acessiblity large content label viewController.tabBarItem.image = tab.image.imageWithoutBaseline() @@ -256,18 +263,18 @@ extension MainTabBarController { // handle push notification. // toggle entry when finish fetch latest notification - Publishers.CombineLatest3( - context.authenticationService.activeMastodonAuthentication, + Publishers.CombineLatest( context.notificationService.unreadNotificationCountDidUpdate, $currentTab ) .receive(on: DispatchQueue.main) - .sink { [weak self] authentication, _, currentTab in + .sink { [weak self] authentication, currentTab in guard let self = self else { return } guard let notificationViewController = self.notificationViewController else { return } + let authentication = self.authContext?.mastodonAuthenticationBox.userAuthorization let hasUnreadPushNotification: Bool = authentication.flatMap { authentication in - let count = UserDefaults.shared.getNotificationCountWithAccessToken(accessToken: authentication.userAccessToken) + let count = UserDefaults.shared.getNotificationCountWithAccessToken(accessToken: authentication.accessToken) return count > 0 } ?? false @@ -297,43 +304,31 @@ extension MainTabBarController { ) } .store(in: &disposeBag) - context.authenticationService.activeMastodonAuthentication - .receive(on: DispatchQueue.main) - .sink { [weak self] activeMastodonAuthentication in - guard let self = self else { return } - - if let user = activeMastodonAuthentication?.user { - self.avatarURLObserver = user.publisher(for: \.avatar) - .sink { [weak self, weak user] _ in - guard let self = self else { return } - guard let user = user else { return } - guard user.managedObjectContext != nil else { return } - self.avatarURL = user.avatarImageURL() - } - } else { - self.avatarURLObserver = nil + + if let user = authContext?.mastodonAuthenticationBox.authenticationRecord.object(in: context.managedObjectContext)?.user { + self.avatarURLObserver = user.publisher(for: \.avatar) + .sink { [weak self, weak user] _ in + guard let self = self else { return } + guard let user = user else { return } + guard user.managedObjectContext != nil else { return } + self.avatarURL = user.avatarImageURL() } - - // a11y - let _profileTabItem = self.tabBar.items?.first { item in item.tag == Tab.me.tag } - guard let profileTabItem = _profileTabItem else { return } - - let currentUserDisplayName = activeMastodonAuthentication?.user.displayNameWithFallback ?? "no user" - profileTabItem.accessibilityHint = L10n.Scene.AccountList.tabBarHint(currentUserDisplayName) - } - .store(in: &disposeBag) + + // a11y + let _profileTabItem = self.tabBar.items?.first { item in item.tag == Tab.me.tag } + guard let profileTabItem = _profileTabItem else { return } + let currentUserDisplayName = user.displayNameWithFallback ?? "no user" + profileTabItem.accessibilityHint = L10n.Scene.AccountList.tabBarHint(currentUserDisplayName) + + } else { + self.avatarURLObserver = nil + } let tabBarLongPressGestureRecognizer = UILongPressGestureRecognizer() tabBarLongPressGestureRecognizer.addTarget(self, action: #selector(MainTabBarController.tabBarLongPressGestureRecognizerHandler(_:))) tabBar.addGestureRecognizer(tabBarLongPressGestureRecognizer) - context.authenticationService.activeMastodonAuthenticationBox - .receive(on: DispatchQueue.main) - .sink { [weak self] authenticationBox in - guard let self = self else { return } - self.isReadyForWizardAvatarButton = authenticationBox != nil - } - .store(in: &disposeBag) + self.isReadyForWizardAvatarButton = authContext != nil $currentTab .receive(on: DispatchQueue.main) @@ -374,13 +369,13 @@ extension MainTabBarController { @objc private func composeButtonDidPressed(_ sender: UIButton) { logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public)") - guard let authenticationBox = context.authenticationService.activeMastodonAuthenticationBox.value else { return } + guard let authContext = self.authContext else { return } let composeViewModel = ComposeViewModel( context: context, composeKind: .post, - authenticationBox: authenticationBox + authContext: authContext ) - coordinator.present(scene: .compose(viewModel: composeViewModel), from: nil, transition: .modal(animated: true, completion: nil)) + _ = coordinator.present(scene: .compose(viewModel: composeViewModel), from: nil, transition: .modal(animated: true, completion: nil)) } @objc private func tabBarLongPressGestureRecognizerHandler(_ sender: UILongPressGestureRecognizer) { @@ -402,7 +397,9 @@ extension MainTabBarController { switch tab { case .me: - coordinator.present(scene: .accountList, from: self, transition: .panModal) + guard let authContext = self.authContext else { return } + let accountListViewModel = AccountListViewModel(context: context, authContext: authContext) + _ = coordinator.present(scene: .accountList(viewModel: accountListViewModel), from: self, transition: .panModal) default: break } @@ -726,26 +723,28 @@ extension MainTabBarController { @objc private func showFavoritesKeyCommandHandler(_ sender: UIKeyCommand) { os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function) - let favoriteViewModel = FavoriteViewModel(context: context) - coordinator.present(scene: .favorite(viewModel: favoriteViewModel), from: nil, transition: .show) + guard let authContext = self.authContext else { return } + let favoriteViewModel = FavoriteViewModel(context: context, authContext: authContext) + _ = coordinator.present(scene: .favorite(viewModel: favoriteViewModel), from: nil, transition: .show) } @objc private func openSettingsKeyCommandHandler(_ sender: UIKeyCommand) { os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function) + guard let authContext = self.authContext else { return } guard let setting = context.settingService.currentSetting.value else { return } - let settingsViewModel = SettingsViewModel(context: context, setting: setting) - coordinator.present(scene: .settings(viewModel: settingsViewModel), from: nil, transition: .modal(animated: true, completion: nil)) + let settingsViewModel = SettingsViewModel(context: context, authContext: authContext, setting: setting) + _ = coordinator.present(scene: .settings(viewModel: settingsViewModel), from: nil, transition: .modal(animated: true, completion: nil)) } @objc private func composeNewPostKeyCommandHandler(_ sender: UIKeyCommand) { 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 authContext = self.authContext else { return } let composeViewModel = ComposeViewModel( context: context, composeKind: .post, - authenticationBox: authenticationBox + authContext: authContext ) - coordinator.present(scene: .compose(viewModel: composeViewModel), from: nil, transition: .modal(animated: true, completion: nil)) + _ = coordinator.present(scene: .compose(viewModel: composeViewModel), from: nil, transition: .modal(animated: true, completion: nil)) } } diff --git a/Mastodon/Scene/Root/RootSplitViewController.swift b/Mastodon/Scene/Root/RootSplitViewController.swift index 3a68d3342..d138f6006 100644 --- a/Mastodon/Scene/Root/RootSplitViewController.swift +++ b/Mastodon/Scene/Root/RootSplitViewController.swift @@ -37,6 +37,10 @@ final class RootSplitViewController: UISplitViewController, NeedsDependency { let searchViewController = SearchViewController() searchViewController.context = context searchViewController.coordinator = coordinator + searchViewController.viewModel = .init( + context: context, + authContext: authContext + ) return searchViewController }() diff --git a/Mastodon/Scene/Root/Sidebar/SidebarViewController.swift b/Mastodon/Scene/Root/Sidebar/SidebarViewController.swift index 86d549373..98006d4c0 100644 --- a/Mastodon/Scene/Root/Sidebar/SidebarViewController.swift +++ b/Mastodon/Scene/Root/Sidebar/SidebarViewController.swift @@ -191,9 +191,10 @@ extension SidebarViewController: UICollectionViewDelegate { case .tab(let tab): delegate?.sidebarViewController(self, didSelectTab: tab) case .setting: + guard let authContext = viewModel.authContext else { return } guard let setting = context.settingService.currentSetting.value else { return } - let settingsViewModel = SettingsViewModel(context: context, setting: setting) - coordinator.present(scene: .settings(viewModel: settingsViewModel), from: self, transition: .modal(animated: true, completion: nil)) + let settingsViewModel = SettingsViewModel(context: context, authContext: authContext, setting: setting) + _ = coordinator.present(scene: .settings(viewModel: settingsViewModel), from: self, transition: .modal(animated: true, completion: nil)) case .compose: assertionFailure() } @@ -201,15 +202,15 @@ extension SidebarViewController: UICollectionViewDelegate { guard let diffableDataSource = viewModel.secondaryDiffableDataSource else { return } guard let item = diffableDataSource.itemIdentifier(for: indexPath) else { return } - guard let authenticationBox = context.authenticationService.activeMastodonAuthenticationBox.value else { return } + guard let authContext = viewModel.authContext else { return } switch item { case .compose: let composeViewModel = ComposeViewModel( context: context, composeKind: .post, - authenticationBox: authenticationBox + authContext: authContext ) - coordinator.present(scene: .compose(viewModel: composeViewModel), from: self, transition: .modal(animated: true, completion: nil)) + _ = coordinator.present(scene: .compose(viewModel: composeViewModel), from: self, transition: .modal(animated: true, completion: nil)) default: assertionFailure() } diff --git a/Mastodon/Scene/Root/Sidebar/SidebarViewModel.swift b/Mastodon/Scene/Root/Sidebar/SidebarViewModel.swift index 967d10b09..9f0eb1899 100644 --- a/Mastodon/Scene/Root/Sidebar/SidebarViewModel.swift +++ b/Mastodon/Scene/Root/Sidebar/SidebarViewModel.swift @@ -21,6 +21,7 @@ final class SidebarViewModel { // input let context: AppContext + let authContext: AuthContext? @Published private var isSidebarDataSourceReady = false @Published private var isAvatarButtonDataReady = false @Published var currentTab: MainTabBarController.Tab = .home @@ -30,10 +31,9 @@ final class SidebarViewModel { var secondaryDiffableDataSource: UICollectionViewDiffableDataSource? @Published private(set) var isReadyForWizardAvatarButton = false - let activeMastodonAuthenticationObjectID = CurrentValueSubject(nil) - - init(context: AppContext) { + init(context: AppContext, authContext: AuthContext?) { self.context = context + self.authContext = authContext Publishers.CombineLatest( $isSidebarDataSourceReady, @@ -42,16 +42,7 @@ final class SidebarViewModel { .map { $0 && $1 } .assign(to: &$isReadyForWizardAvatarButton) - context.authenticationService.activeMastodonAuthentication - .sink { [weak self] authentication in - guard let self = self else { return } - - // bind objectID - self.activeMastodonAuthenticationObjectID.value = authentication?.objectID - - self.isAvatarButtonDataReady = authentication != nil - } - .store(in: &disposeBag) + self.isAvatarButtonDataReady = authContext != nil } } @@ -81,8 +72,8 @@ extension SidebarViewModel { let imageURL: URL? = { switch item { case .me: - let authentication = self.context.authenticationService.activeMastodonAuthentication.value - return authentication?.user.avatarImageURL() + let user = self.authContext?.mastodonAuthenticationBox.authenticationRecord.object(in: self.context.managedObjectContext)?.user + return user?.avatarImageURL() default: return nil } @@ -109,18 +100,19 @@ extension SidebarViewModel { switch item { case .notification: - Publishers.CombineLatest3( - self.context.authenticationService.activeMastodonAuthentication, + Publishers.CombineLatest( self.context.notificationService.unreadNotificationCountDidUpdate, self.$currentTab ) .receive(on: DispatchQueue.main) - .sink { [weak cell] authentication, _, currentTab in + .sink { [weak cell] authentication, currentTab in guard let cell = cell else { return } - let hasUnreadPushNotification: Bool = authentication.flatMap { authentication in - let count = UserDefaults.shared.getNotificationCountWithAccessToken(accessToken: authentication.userAccessToken) + + let hasUnreadPushNotification: Bool = { + guard let accessToken = self.authContext?.mastodonAuthenticationBox.userAuthorization.accessToken else { return false } + let count = UserDefaults.shared.getNotificationCountWithAccessToken(accessToken: accessToken) return count > 0 - } ?? false + }() let image: UIImage = { if currentTab == .notification { @@ -135,8 +127,8 @@ extension SidebarViewModel { } .store(in: &cell.disposeBag) case .me: - guard let authentication = self.context.authenticationService.activeMastodonAuthentication.value else { break } - let currentUserDisplayName = authentication.user.displayNameWithFallback + guard let user = self.authContext?.mastodonAuthenticationBox.authenticationRecord.object(in: self.context.managedObjectContext)?.user else { return } + let currentUserDisplayName = user.displayNameWithFallback cell.accessibilityHint = L10n.Scene.AccountList.tabBarHint(currentUserDisplayName) default: break diff --git a/Mastodon/Scene/Search/Search/SearchViewController.swift b/Mastodon/Scene/Search/Search/SearchViewController.swift index 947c1593d..32f3dbe3d 100644 --- a/Mastodon/Scene/Search/Search/SearchViewController.swift +++ b/Mastodon/Scene/Search/Search/SearchViewController.swift @@ -30,7 +30,7 @@ final class SearchViewController: UIViewController, NeedsDependency { var searchTransitionController = SearchTransitionController() var disposeBag = Set() - private(set) lazy var viewModel = SearchViewModel(context: context) + var viewModel: SearchViewModel! // use AutoLayout could set search bar margin automatically to // layout alongside with split mode button (on iPad) @@ -49,10 +49,16 @@ final class SearchViewController: UIViewController, NeedsDependency { let searchBarTapPublisher = PassthroughSubject() - private(set) lazy var discoveryViewController: DiscoveryViewController = { + private(set) lazy var discoveryViewController: DiscoveryViewController? = { + guard let authContext = viewModel.authContext else { return nil } let viewController = DiscoveryViewController() viewController.context = context viewController.coordinator = coordinator + viewController.viewModel = .init( + context: context, + coordinator: coordinator, + authContext: authContext + ) return viewController }() @@ -93,6 +99,8 @@ extension SearchViewController { // collectionView: collectionView // ) + guard let discoveryViewController = self.discoveryViewController else { return } + addChild(discoveryViewController) discoveryViewController.view.translatesAutoresizingMaskIntoConstraints = false view.addSubview(discoveryViewController.view) @@ -143,7 +151,8 @@ extension SearchViewController { .sink { [weak self] in guard let self = self else { return } // push to search detail - let searchDetailViewModel = SearchDetailViewModel() + guard let authContext = self.viewModel.authContext else { return } + let searchDetailViewModel = SearchDetailViewModel(authContext: authContext) searchDetailViewModel.needsBecomeFirstResponder = true self.navigationController?.delegate = self.searchTransitionController // FIXME: diff --git a/Mastodon/Scene/Search/Search/SearchViewModel.swift b/Mastodon/Scene/Search/Search/SearchViewModel.swift index b0eccd49b..51d614280 100644 --- a/Mastodon/Scene/Search/Search/SearchViewModel.swift +++ b/Mastodon/Scene/Search/Search/SearchViewModel.swift @@ -20,14 +20,16 @@ final class SearchViewModel: NSObject { // input let context: AppContext + let authContext: AuthContext? let viewDidAppeared = PassthroughSubject() // output var diffableDataSource: UICollectionViewDiffableDataSource? @Published var hashtags: [Mastodon.Entity.Tag] = [] - init(context: AppContext) { + init(context: AppContext, authContext: AuthContext?) { self.context = context + self.authContext = authContext super.init() // Publishers.CombineLatest( diff --git a/Mastodon/Scene/Search/SearchDetail/SearchDetailViewController.swift b/Mastodon/Scene/Search/SearchDetail/SearchDetailViewController.swift index 0b0a0d003..6ffc90182 100644 --- a/Mastodon/Scene/Search/SearchDetail/SearchDetailViewController.swift +++ b/Mastodon/Scene/Search/SearchDetail/SearchDetailViewController.swift @@ -83,7 +83,7 @@ final class SearchDetailViewController: PageboyViewController, NeedsDependency { let searchHistoryViewController = SearchHistoryViewController() searchHistoryViewController.context = context searchHistoryViewController.coordinator = coordinator - searchHistoryViewController.viewModel = SearchHistoryViewModel(context: context) + searchHistoryViewController.viewModel = SearchHistoryViewModel(context: context, authContext: viewModel.authContext) return searchHistoryViewController }() } @@ -131,7 +131,7 @@ extension SearchDetailViewController { let searchResultViewController = SearchResultViewController() searchResultViewController.context = context searchResultViewController.coordinator = coordinator - searchResultViewController.viewModel = SearchResultViewModel(context: context, searchScope: scope) + searchResultViewController.viewModel = SearchResultViewModel(context: context, authContext: viewModel.authContext, searchScope: scope) // bind searchText viewModel.searchText diff --git a/Mastodon/Scene/Search/SearchDetail/SearchDetailViewModel.swift b/Mastodon/Scene/Search/SearchDetail/SearchDetailViewModel.swift index 140fe14e8..779aaa2dc 100644 --- a/Mastodon/Scene/Search/SearchDetail/SearchDetailViewModel.swift +++ b/Mastodon/Scene/Search/SearchDetail/SearchDetailViewModel.swift @@ -10,12 +10,14 @@ import Foundation import CoreGraphics import Combine import MastodonSDK +import MastodonCore import MastodonAsset import MastodonLocalization final class SearchDetailViewModel { // input + let authContext: AuthContext var needsBecomeFirstResponder = false let viewDidAppear = PassthroughSubject() let navigationBarFrame = CurrentValueSubject(.zero) @@ -26,7 +28,8 @@ final class SearchDetailViewModel { let searchText: CurrentValueSubject let searchActionPublisher = PassthroughSubject() - init(initialSearchText: String = "") { + init(authContext: AuthContext, initialSearchText: String = "") { + self.authContext = authContext self.searchText = CurrentValueSubject(initialSearchText) } diff --git a/Mastodon/Scene/Search/SearchDetail/SearchHistory/SearchHistoryViewController.swift b/Mastodon/Scene/Search/SearchDetail/SearchHistory/SearchHistoryViewController.swift index 7d5f6c60e..52d0ffb9c 100644 --- a/Mastodon/Scene/Search/SearchDetail/SearchHistory/SearchHistoryViewController.swift +++ b/Mastodon/Scene/Search/SearchDetail/SearchHistory/SearchHistoryViewController.swift @@ -109,6 +109,11 @@ extension SearchHistoryViewController: UICollectionViewDelegate { } +// MARK: - AuthContextProvider +extension SearchHistoryViewController: AuthContextProvider { + var authContext: AuthContext { viewModel.authContext } +} + // MARK: - SearchHistorySectionHeaderCollectionReusableViewDelegate extension SearchHistoryViewController: SearchHistorySectionHeaderCollectionReusableViewDelegate { func searchHistorySectionHeaderCollectionReusableView( diff --git a/Mastodon/Scene/Search/SearchDetail/SearchHistory/SearchHistoryViewModel.swift b/Mastodon/Scene/Search/SearchDetail/SearchHistory/SearchHistoryViewModel.swift index b7987413f..1ec06ebe7 100644 --- a/Mastodon/Scene/Search/SearchDetail/SearchHistory/SearchHistoryViewModel.swift +++ b/Mastodon/Scene/Search/SearchDetail/SearchHistory/SearchHistoryViewModel.swift @@ -17,23 +17,19 @@ final class SearchHistoryViewModel { // input let context: AppContext + let authContext: AuthContext let searchHistoryFetchedResultController: SearchHistoryFetchedResultController // output var diffableDataSource: UICollectionViewDiffableDataSource? - init(context: AppContext) { + init(context: AppContext, authContext: AuthContext) { self.context = context + self.authContext = authContext self.searchHistoryFetchedResultController = SearchHistoryFetchedResultController(managedObjectContext: context.managedObjectContext) - context.authenticationService.activeMastodonAuthenticationBox - .receive(on: DispatchQueue.main) - .sink { [weak self] box in - guard let self = self else { return } - self.searchHistoryFetchedResultController.domain.value = box?.domain - self.searchHistoryFetchedResultController.userID.value = box?.userID - } - .store(in: &disposeBag) + searchHistoryFetchedResultController.domain.value = authContext.mastodonAuthenticationBox.domain + searchHistoryFetchedResultController.userID.value = authContext.mastodonAuthenticationBox.userID } } diff --git a/Mastodon/Scene/Search/SearchDetail/SearchResult/SearchResultViewController.swift b/Mastodon/Scene/Search/SearchDetail/SearchResult/SearchResultViewController.swift index 87c981071..67de62bf3 100644 --- a/Mastodon/Scene/Search/SearchDetail/SearchResult/SearchResultViewController.swift +++ b/Mastodon/Scene/Search/SearchDetail/SearchResult/SearchResultViewController.swift @@ -154,10 +154,9 @@ extension SearchResultViewController { } // MARK: - StatusTableViewCellDelegate -//extension SearchResultViewController: StatusTableViewCellDelegate { -// weak var playerViewControllerDelegate: AVPlayerViewControllerDelegate? { return self } -// func parent() -> UIViewController { return self } -//} +extension SearchResultViewController: AuthContextProvider { + var authContext: AuthContext { viewModel.authContext } +} // MARK: - UITableViewDelegate extension SearchResultViewController: UITableViewDelegate, AutoGenerateTableViewDelegate { diff --git a/Mastodon/Scene/Search/SearchDetail/SearchResult/SearchResultViewModel+Diffable.swift b/Mastodon/Scene/Search/SearchDetail/SearchResult/SearchResultViewModel+Diffable.swift index ff64b80f0..7d243b1fa 100644 --- a/Mastodon/Scene/Search/SearchDetail/SearchResult/SearchResultViewModel+Diffable.swift +++ b/Mastodon/Scene/Search/SearchDetail/SearchResult/SearchResultViewModel+Diffable.swift @@ -18,6 +18,7 @@ extension SearchResultViewModel { tableView: tableView, context: context, configuration: .init( + authContext: authContext, statusViewTableViewCellDelegate: statusTableViewCellDelegate ) ) diff --git a/Mastodon/Scene/Search/SearchDetail/SearchResult/SearchResultViewModel+State.swift b/Mastodon/Scene/Search/SearchDetail/SearchResult/SearchResultViewModel+State.swift index cd1579747..b5deb777c 100644 --- a/Mastodon/Scene/Search/SearchDetail/SearchResult/SearchResultViewModel+State.swift +++ b/Mastodon/Scene/Search/SearchDetail/SearchResult/SearchResultViewModel+State.swift @@ -73,11 +73,6 @@ extension SearchResultViewModel.State { override func didEnter(from previousState: GKState?) { 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 - } let searchText = viewModel.searchText.value let searchType = viewModel.searchScope.searchType @@ -133,7 +128,7 @@ extension SearchResultViewModel.State { do { let response = try await viewModel.context.apiService.search( query: query, - authenticationBox: authenticationBox + authenticationBox: viewModel.authContext.mastodonAuthenticationBox ) // discard result when search text is outdated diff --git a/Mastodon/Scene/Search/SearchDetail/SearchResult/SearchResultViewModel.swift b/Mastodon/Scene/Search/SearchDetail/SearchResult/SearchResultViewModel.swift index a7b97de6b..546920749 100644 --- a/Mastodon/Scene/Search/SearchDetail/SearchResult/SearchResultViewModel.swift +++ b/Mastodon/Scene/Search/SearchDetail/SearchResult/SearchResultViewModel.swift @@ -20,6 +20,7 @@ final class SearchResultViewModel { // input let context: AppContext + let authContext: AuthContext let searchScope: SearchDetailViewModel.SearchScope let searchText = CurrentValueSubject("") @Published var hashtags: [Mastodon.Entity.Tag] = [] @@ -48,30 +49,21 @@ final class SearchResultViewModel { }() let didDataSourceUpdate = PassthroughSubject() - init(context: AppContext, searchScope: SearchDetailViewModel.SearchScope) { + init(context: AppContext, authContext: AuthContext, searchScope: SearchDetailViewModel.SearchScope) { self.context = context + self.authContext = authContext self.searchScope = searchScope self.userFetchedResultsController = UserFetchedResultsController( managedObjectContext: context.managedObjectContext, - domain: nil, + domain: authContext.mastodonAuthenticationBox.domain, additionalPredicate: nil ) self.statusFetchedResultsController = StatusFetchedResultsController( managedObjectContext: context.managedObjectContext, - domain: nil, + domain: authContext.mastodonAuthenticationBox.domain, additionalTweetPredicate: nil ) - context.authenticationService.activeMastodonAuthenticationBox - .map { $0?.domain } - .assign(to: \.domain, on: userFetchedResultsController) - .store(in: &disposeBag) - - context.authenticationService.activeMastodonAuthenticationBox - .map { $0?.domain } - .assign(to: \.domain, on: statusFetchedResultsController) - .store(in: &disposeBag) - // Publishers.CombineLatest( // items, // statusFetchedResultsController.objectIDs.removeDuplicates() diff --git a/Mastodon/Scene/Settings/SettingsViewController.swift b/Mastodon/Scene/Settings/SettingsViewController.swift index edafbe1a3..53a856fd0 100644 --- a/Mastodon/Scene/Settings/SettingsViewController.swift +++ b/Mastodon/Scene/Settings/SettingsViewController.swift @@ -281,7 +281,7 @@ extension SettingsViewController { } alertController.addAction(cancelAction) alertController.addAction(signOutAction) - self.coordinator.present( + _ = self.coordinator.present( scene: .alertController(alertController: alertController), from: self, transition: .alertController(animated: true, completion: nil) @@ -289,15 +289,13 @@ extension SettingsViewController { } func signOut() { - guard let authenticationBox = context.authenticationService.activeMastodonAuthenticationBox.value else { - return - } - // clear badge before sign-out context.notificationService.clearNotificationCountForActiveUser() Task { @MainActor in - try await context.authenticationService.signOutMastodonUser(authenticationBox: authenticationBox) + try await context.authenticationService.signOutMastodonUser( + authenticationBox: viewModel.authContext.mastodonAuthenticationBox + ) self.coordinator.setup() } } @@ -372,12 +370,12 @@ extension SettingsViewController: UITableViewDelegate { feedbackGenerator.impactOccurred() switch link { case .accountSettings: - guard let box = context.authenticationService.activeMastodonAuthenticationBox.value, - let url = URL(string: "https://\(box.domain)/auth/edit") else { return } + let domain = viewModel.authContext.mastodonAuthenticationBox.domain + guard let url = URL(string: "https://\(domain)/auth/edit") else { return } viewModel.openAuthenticationPage(authenticateURL: url, presentationContextProvider: self) case .github: guard let url = URL(string: "https://github.com/mastodon/mastodon-ios") else { break } - coordinator.present( + _ = coordinator.present( scene: .safari(url: url), from: self, transition: .safariPresent(animated: true, completion: nil) @@ -385,7 +383,7 @@ extension SettingsViewController: UITableViewDelegate { case .termsOfService, .privacyPolicy: // same URL guard let url = viewModel.privacyURL else { break } - coordinator.present( + _ = coordinator.present( scene: .safari(url: url), from: self, transition: .safariPresent(animated: true, completion: nil) diff --git a/Mastodon/Scene/Settings/SettingsViewModel.swift b/Mastodon/Scene/Settings/SettingsViewModel.swift index ea2275429..8d737b93b 100644 --- a/Mastodon/Scene/Settings/SettingsViewModel.swift +++ b/Mastodon/Scene/Settings/SettingsViewModel.swift @@ -19,10 +19,11 @@ class SettingsViewModel { var disposeBag = Set() + // input let context: AppContext + let authContext: AuthContext var mastodonAuthenticationController: MastodonAuthenticationController? - // input let setting: CurrentValueSubject var updateDisposeBag = Set() var createDisposeBag = Set() @@ -42,15 +43,13 @@ class SettingsViewModel { let updateSubscriptionSubject = PassthroughSubject<(triggerBy: String, values: [Bool?]), Never>() lazy var privacyURL: URL? = { - guard let box = AppContext.shared.authenticationService.activeMastodonAuthenticationBox.value else { - return nil - } - - return Mastodon.API.privacyURL(domain: box.domain) + let domain = authContext.mastodonAuthenticationBox.domain + return Mastodon.API.privacyURL(domain: domain) }() - init(context: AppContext, setting: Setting) { + init(context: AppContext, authContext: AuthContext, setting: Setting) { self.context = context + self.authContext = authContext self.setting = CurrentValueSubject(setting) self.setting @@ -60,10 +59,7 @@ class SettingsViewModel { }) .store(in: &disposeBag) - context.authenticationService.activeMastodonAuthenticationBox - .compactMap { $0?.domain } - .map { context.apiService.instance(domain: $0) } - .switchToLatest() + context.apiService.instance(domain: authContext.mastodonAuthenticationBox.domain) .sink { [weak self] completion in guard let self = self else { return } switch completion { diff --git a/Mastodon/Scene/Share/View/Content/NotificationView+Configuration.swift b/Mastodon/Scene/Share/View/Content/NotificationView+Configuration.swift index ed5e68dc7..98d06fd92 100644 --- a/Mastodon/Scene/Share/View/Content/NotificationView+Configuration.swift +++ b/Mastodon/Scene/Share/View/Content/NotificationView+Configuration.swift @@ -162,42 +162,39 @@ extension NotificationView { } } .store(in: &disposeBag) + + let authContext = viewModel.authContext // isMuting - Publishers.CombineLatest( - viewModel.$userIdentifier, - author.publisher(for: \.mutingBy) - ) - .map { userIdentifier, mutingBy in - guard let userIdentifier = userIdentifier else { return false } - return mutingBy.contains(where: { - $0.id == userIdentifier.userID && $0.domain == userIdentifier.domain - }) - } - .assign(to: \.isMuting, on: viewModel) - .store(in: &disposeBag) + author.publisher(for: \.mutingBy) + .map { mutingBy in + guard let authContext = authContext else { return false } + return mutingBy.contains(where: { + $0.id == authContext.mastodonAuthenticationBox.userID + && $0.domain == authContext.mastodonAuthenticationBox.domain + }) + } + .assign(to: \.isMuting, on: viewModel) + .store(in: &disposeBag) // isBlocking - Publishers.CombineLatest( - viewModel.$userIdentifier, - author.publisher(for: \.blockingBy) - ) - .map { userIdentifier, blockingBy in - guard let userIdentifier = userIdentifier else { return false } - return blockingBy.contains(where: { - $0.id == userIdentifier.userID && $0.domain == userIdentifier.domain - }) - } - .assign(to: \.isBlocking, on: viewModel) - .store(in: &disposeBag) + author.publisher(for: \.blockingBy) + .map { blockingBy in + guard let authContext = authContext else { return false } + return blockingBy.contains(where: { + $0.id == authContext.mastodonAuthenticationBox.userID + && $0.domain == authContext.mastodonAuthenticationBox.domain + }) + } + .assign(to: \.isBlocking, on: viewModel) + .store(in: &disposeBag) // isMyself - Publishers.CombineLatest3( - viewModel.$userIdentifier, + Publishers.CombineLatest( author.publisher(for: \.domain), author.publisher(for: \.id) ) - .map { userIdentifier, domain, id in - guard let userIdentifier = userIdentifier else { return false } - return userIdentifier.domain == domain - && userIdentifier.userID == id + .map { domain, id in + guard let authContext = authContext else { return false } + return authContext.mastodonAuthenticationBox.domain == domain + && authContext.mastodonAuthenticationBox.userID == id } .assign(to: \.isMyself, on: viewModel) .store(in: &disposeBag) diff --git a/Mastodon/Scene/Share/View/Content/PollOptionView+Configuration.swift b/Mastodon/Scene/Share/View/Content/PollOptionView+Configuration.swift index 7d1baa188..334c9ce15 100644 --- a/Mastodon/Scene/Share/View/Content/PollOptionView+Configuration.swift +++ b/Mastodon/Scene/Share/View/Content/PollOptionView+Configuration.swift @@ -57,13 +57,13 @@ extension PollOptionView { option.publisher(for: \.poll), option.publisher(for: \.votedBy), option.publisher(for: \.isSelected), - viewModel.$userIdentifier + viewModel.$authContext ) - .sink { [weak self] poll, optionVotedBy, isSelected, userIdentifier in + .sink { [weak self] poll, optionVotedBy, isSelected, authContext in guard let self = self else { return } - let domain = userIdentifier?.domain ?? "" - let userID = userIdentifier?.userID ?? "" + let domain = authContext?.mastodonAuthenticationBox.domain ?? "" + let userID = authContext?.mastodonAuthenticationBox.userID ?? "" let options = poll.options let pollVoteBy = poll.votedBy ?? Set() diff --git a/Mastodon/Scene/Share/View/Content/StatusView+Configuration.swift b/Mastodon/Scene/Share/View/Content/StatusView+Configuration.swift index 78ca7b88d..590b85652 100644 --- a/Mastodon/Scene/Share/View/Content/StatusView+Configuration.swift +++ b/Mastodon/Scene/Share/View/Content/StatusView+Configuration.swift @@ -122,7 +122,7 @@ extension StatusView { let header = createHeader(name: nil, emojis: nil) viewModel.header = header - if let authenticationBox = AppContext.shared.authenticationService.activeMastodonAuthenticationBox.value { + if let authenticationBox = viewModel.authContext?.mastodonAuthenticationBox { Just(inReplyToAccountID) .asyncMap { userID in return try await AppContext.shared.apiService.accountInfo( diff --git a/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewController.swift b/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewController.swift index 3dfffa540..6e3e73b8a 100644 --- a/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewController.swift +++ b/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewController.swift @@ -159,7 +159,7 @@ extension SuggestionAccountViewController: UITableViewDelegate { switch item { case .account(let record): guard let account = record.object(in: context.managedObjectContext) else { return } - let cachedProfileViewModel = CachedProfileViewModel(context: context, mastodonUser: account) + let cachedProfileViewModel = CachedProfileViewModel(context: context, authContext: viewModel.authContext, mastodonUser: account) coordinator.present( scene: .profile(viewModel: cachedProfileViewModel), from: self, @@ -169,6 +169,12 @@ extension SuggestionAccountViewController: UITableViewDelegate { } } +// MARK: - AuthContextProvider +extension SuggestionAccountViewController: AuthContextProvider { + var authContext: AuthContext { viewModel.authContext } +} + +// MARK: - SuggestionAccountTableViewCellDelegate extension SuggestionAccountViewController: SuggestionAccountTableViewCellDelegate { func suggestionAccountTableViewCell( _ cell: SuggestionAccountTableViewCell, @@ -177,7 +183,6 @@ extension SuggestionAccountViewController: SuggestionAccountTableViewCellDelegat guard let tableViewDiffableDataSource = viewModel.tableViewDiffableDataSource else { return } guard let indexPath = tableView.indexPath(for: cell) else { return } guard let item = tableViewDiffableDataSource.itemIdentifier(for: indexPath) else { return } - guard let authenticationBox = context.authenticationService.activeMastodonAuthenticationBox.value else { return } switch item { case .account(let user): @@ -186,8 +191,7 @@ extension SuggestionAccountViewController: SuggestionAccountTableViewCellDelegat do { try await DataSourceFacade.responseToUserFollowAction( dependency: self, - user: user, - authenticationBox: authenticationBox + user: user ) } catch { // do noting diff --git a/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewModel+Diffable.swift b/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewModel+Diffable.swift index 4496b9f0a..35ba305bc 100644 --- a/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewModel+Diffable.swift +++ b/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewModel+Diffable.swift @@ -17,6 +17,7 @@ extension SuggestionAccountViewModel { tableView: tableView, context: context, configuration: RecommendAccountSection.Configuration( + authContext: authContext, suggestionAccountTableViewCellDelegate: suggestionAccountTableViewCellDelegate ) ) diff --git a/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewModel.swift b/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewModel.swift index 70676bf0f..b8af80bb4 100644 --- a/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewModel.swift +++ b/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewModel.swift @@ -25,6 +25,7 @@ final class SuggestionAccountViewModel: NSObject { // input let context: AppContext + let authContext: AuthContext let userFetchedResultsController: UserFetchedResultsController let selectedUserFetchedResultsController: UserFetchedResultsController @@ -35,9 +36,11 @@ final class SuggestionAccountViewModel: NSObject { var tableViewDiffableDataSource: UITableViewDiffableDataSource? init( - context: AppContext + context: AppContext, + authContext: AuthContext ) { self.context = context + self.authContext = authContext self.userFetchedResultsController = UserFetchedResultsController( managedObjectContext: context.managedObjectContext, domain: nil, @@ -50,14 +53,11 @@ final class SuggestionAccountViewModel: NSObject { ) super.init() - guard let authenticationBox = context.authenticationService.activeMastodonAuthenticationBox.value else { - return - } - userFetchedResultsController.domain = authenticationBox.domain - selectedUserFetchedResultsController.domain = authenticationBox.domain + userFetchedResultsController.domain = authContext.mastodonAuthenticationBox.domain + selectedUserFetchedResultsController.domain = authContext.mastodonAuthenticationBox.domain selectedUserFetchedResultsController.additionalPredicate = NSCompoundPredicate(orPredicateWithSubpredicates: [ - MastodonUser.predicate(followingBy: authenticationBox.userID), - MastodonUser.predicate(followRequestedBy: authenticationBox.userID) + MastodonUser.predicate(followingBy: authContext.mastodonAuthenticationBox.userID), + MastodonUser.predicate(followRequestedBy: authContext.mastodonAuthenticationBox.userID) ]) // fetch recomment users @@ -66,13 +66,13 @@ final class SuggestionAccountViewModel: NSObject { do { let response = try await context.apiService.suggestionAccountV2( query: nil, - authenticationBox: authenticationBox + authenticationBox: authContext.mastodonAuthenticationBox ) userIDs = response.value.map { $0.account.id } } catch let error as Mastodon.API.Error where error.httpResponseStatus == .notFound { let response = try await context.apiService.suggestionAccount( query: nil, - authenticationBox: authenticationBox + authenticationBox: authContext.mastodonAuthenticationBox ) userIDs = response.value.map { $0.id } } catch { @@ -90,12 +90,9 @@ final class SuggestionAccountViewModel: NSObject { .sink { [weak self] records in guard let _ = self else { return } Task { - guard let authenticationBox = context.authenticationService.activeMastodonAuthenticationBox.value else { - return - } _ = try await context.apiService.relationship( records: records, - authenticationBox: authenticationBox + authenticationBox: authContext.mastodonAuthenticationBox ) } } diff --git a/Mastodon/Scene/Thread/CachedThreadViewModel.swift b/Mastodon/Scene/Thread/CachedThreadViewModel.swift index 0a39db590..00c29e157 100644 --- a/Mastodon/Scene/Thread/CachedThreadViewModel.swift +++ b/Mastodon/Scene/Thread/CachedThreadViewModel.swift @@ -10,10 +10,11 @@ import CoreDataStack import MastodonCore final class CachedThreadViewModel: ThreadViewModel { - init(context: AppContext, status: Status) { + init(context: AppContext, authContext: AuthContext, status: Status) { let threadContext = StatusItem.Thread.Context(status: .init(objectID: status.objectID)) super.init( context: context, + authContext: authContext, optionalRoot: .root(context: threadContext) ) } diff --git a/Mastodon/Scene/Thread/RemoteThreadViewModel.swift b/Mastodon/Scene/Thread/RemoteThreadViewModel.swift index da5a9bba0..e22b11961 100644 --- a/Mastodon/Scene/Thread/RemoteThreadViewModel.swift +++ b/Mastodon/Scene/Thread/RemoteThreadViewModel.swift @@ -15,22 +15,20 @@ final class RemoteThreadViewModel: ThreadViewModel { init( context: AppContext, + authContext: AuthContext, statusID: Mastodon.Entity.Status.ID ) { super.init( context: context, + authContext: authContext, optionalRoot: nil ) - guard let authenticationBox = context.authenticationService.activeMastodonAuthenticationBox.value else { - return - } - Task { @MainActor in - let domain = authenticationBox.domain + let domain = authContext.mastodonAuthenticationBox.domain let response = try await context.apiService.status( statusID: statusID, - authenticationBox: authenticationBox + authenticationBox: authContext.mastodonAuthenticationBox ) let managedObjectContext = context.managedObjectContext @@ -49,22 +47,20 @@ final class RemoteThreadViewModel: ThreadViewModel { init( context: AppContext, + authContext: AuthContext, notificationID: Mastodon.Entity.Notification.ID ) { super.init( context: context, + authContext: authContext, optionalRoot: nil ) - guard let authenticationBox = context.authenticationService.activeMastodonAuthenticationBox.value else { - return - } - Task { @MainActor in - let domain = authenticationBox.domain + let domain = authContext.mastodonAuthenticationBox.domain let response = try await context.apiService.notification( notificationID: notificationID, - authenticationBox: authenticationBox + authenticationBox: authContext.mastodonAuthenticationBox ) guard let statusID = response.value.status?.id else { return } diff --git a/Mastodon/Scene/Thread/ThreadViewController.swift b/Mastodon/Scene/Thread/ThreadViewController.swift index 7915df6e5..fc158919b 100644 --- a/Mastodon/Scene/Thread/ThreadViewController.swift +++ b/Mastodon/Scene/Thread/ThreadViewController.swift @@ -112,13 +112,12 @@ extension ThreadViewController { @objc private func replyBarButtonItemPressed(_ sender: UIBarButtonItem) { logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public)") guard case let .root(threadContext) = viewModel.root else { return } - guard let authenticationBox = context.authenticationService.activeMastodonAuthenticationBox.value else { return } let composeViewModel = ComposeViewModel( context: context, composeKind: .reply(status: threadContext.status), - authenticationBox: authenticationBox + authContext: viewModel.authContext ) - coordinator.present( + _ = coordinator.present( scene: .compose(viewModel: composeViewModel), from: self, transition: .modal(animated: true, completion: nil) @@ -126,8 +125,10 @@ extension ThreadViewController { } } -//// MARK: - StatusTableViewControllerAspect -//extension ThreadViewController: StatusTableViewControllerAspect { } +// MARK: - AuthContextProvider +extension ThreadViewController: AuthContextProvider { + var authContext: AuthContext { viewModel.authContext } +} // MARK: - UITableViewDelegate extension ThreadViewController: UITableViewDelegate, AutoGenerateTableViewDelegate { @@ -178,7 +179,6 @@ extension ThreadViewController: UITableViewDelegate, AutoGenerateTableViewDelega // MARK: - StatusTableViewCellDelegate extension ThreadViewController: StatusTableViewCellDelegate { } - extension ThreadViewController { override var keyCommands: [UIKeyCommand]? { return navigationKeyCommands + statusNavigationKeyCommands diff --git a/Mastodon/Scene/Thread/ThreadViewModel+Diffable.swift b/Mastodon/Scene/Thread/ThreadViewModel+Diffable.swift index a865dd8f0..7040818e9 100644 --- a/Mastodon/Scene/Thread/ThreadViewModel+Diffable.swift +++ b/Mastodon/Scene/Thread/ThreadViewModel+Diffable.swift @@ -23,6 +23,7 @@ extension ThreadViewModel { tableView: tableView, context: context, configuration: StatusSection.Configuration( + authContext: authContext, statusTableViewCellDelegate: statusTableViewCellDelegate, timelineMiddleLoaderTableViewCellDelegate: nil, filterContext: .thread, diff --git a/Mastodon/Scene/Thread/ThreadViewModel+LoadThreadState.swift b/Mastodon/Scene/Thread/ThreadViewModel+LoadThreadState.swift index 86fdc2111..4917aacb5 100644 --- a/Mastodon/Scene/Thread/ThreadViewModel+LoadThreadState.swift +++ b/Mastodon/Scene/Thread/ThreadViewModel+LoadThreadState.swift @@ -69,11 +69,7 @@ extension ThreadViewModel.LoadThreadState { super.didEnter(from: previousState) guard let viewModel = viewModel, let stateMachine = stateMachine else { return } - guard let authenticationBox = viewModel.context.authenticationService.activeMastodonAuthenticationBox.value else { - stateMachine.enter(Fail.self) - return - } - + guard let threadContext = viewModel.threadContext else { stateMachine.enter(Fail.self) return @@ -83,7 +79,7 @@ extension ThreadViewModel.LoadThreadState { do { let response = try await viewModel.context.apiService.statusContext( statusID: threadContext.statusID, - authenticationBox: authenticationBox + authenticationBox: viewModel.authContext.mastodonAuthenticationBox ) await enter(state: NoMore.self) diff --git a/Mastodon/Scene/Thread/ThreadViewModel.swift b/Mastodon/Scene/Thread/ThreadViewModel.swift index 54c9d1599..735d85cd4 100644 --- a/Mastodon/Scene/Thread/ThreadViewModel.swift +++ b/Mastodon/Scene/Thread/ThreadViewModel.swift @@ -26,6 +26,7 @@ class ThreadViewModel { // input let context: AppContext + let authContext: AuthContext let mastodonStatusThreadViewModel: MastodonStatusThreadViewModel // let cellFrameCache = NSCache() @@ -54,9 +55,11 @@ class ThreadViewModel { init( context: AppContext, + authContext: AuthContext, optionalRoot: StatusItem.Thread? ) { self.context = context + self.authContext = authContext self.root = optionalRoot self.mastodonStatusThreadViewModel = MastodonStatusThreadViewModel(context: context) // self.rootNode = CurrentValueSubject(optionalStatus.flatMap { RootNode(domain: $0.domain, statusID: $0.id, replyToID: $0.inReplyToID) }) diff --git a/Mastodon/Supporting Files/SceneDelegate.swift b/Mastodon/Supporting Files/SceneDelegate.swift index c1e6d7abe..f368fa246 100644 --- a/Mastodon/Supporting Files/SceneDelegate.swift +++ b/Mastodon/Supporting Files/SceneDelegate.swift @@ -146,11 +146,11 @@ extension SceneDelegate { if coordinator?.tabBarController.topMost is ComposeViewController { logger.debug("\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): composing…") } else { - if let authenticationBox = AppContext.shared.authenticationService.activeMastodonAuthenticationBox.value { + if let authContext = coordinator?.authContext { let composeViewModel = ComposeViewModel( context: AppContext.shared, composeKind: .post, - authenticationBox: authenticationBox + authContext: authContext ) coordinator?.present(scene: .compose(viewModel: composeViewModel), from: nil, transition: .modal(animated: true, completion: nil)) logger.debug("\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): present compose scene") diff --git a/MastodonSDK/Sources/CoreDataStack/Utility/ManagedObjectRecord.swift b/MastodonSDK/Sources/CoreDataStack/Utility/ManagedObjectRecord.swift index ea087d894..4910145cf 100644 --- a/MastodonSDK/Sources/CoreDataStack/Utility/ManagedObjectRecord.swift +++ b/MastodonSDK/Sources/CoreDataStack/Utility/ManagedObjectRecord.swift @@ -30,3 +30,9 @@ public class ManagedObjectRecord: Hashable { } } + +extension Managed where Self: NSManagedObject { + public var asRecrod: ManagedObjectRecord { + return .init(objectID: objectID) + } +} diff --git a/MastodonSDK/Sources/MastodonCommon/Preference/Preference+Notification.swift b/MastodonSDK/Sources/MastodonCommon/Preference/Preference+Notification.swift index 7b2ac57a1..38ed3aa5e 100644 --- a/MastodonSDK/Sources/MastodonCommon/Preference/Preference+Notification.swift +++ b/MastodonSDK/Sources/MastodonCommon/Preference/Preference+Notification.swift @@ -1,6 +1,6 @@ // // UserDefaults+Notification.swift -// AppShared +// MastodonCommon // // Created by Cirno MainasuK on 2021-10-9. // diff --git a/MastodonSDK/Sources/MastodonCore/AppSecret.swift b/MastodonSDK/Sources/MastodonCore/AppSecret.swift index 59c7a7cb5..687cb6fca 100644 --- a/MastodonSDK/Sources/MastodonCore/AppSecret.swift +++ b/MastodonSDK/Sources/MastodonCore/AppSecret.swift @@ -1,6 +1,6 @@ // // AppSecret.swift -// MastodonCommon +// MastodonCore // // Created by MainasuK Cirno on 2021-4-27. // diff --git a/MastodonSDK/Sources/MastodonCore/Service/AuthenticationService.swift b/MastodonSDK/Sources/MastodonCore/Service/AuthenticationService.swift index 8d69c3558..afb4e63a9 100644 --- a/MastodonSDK/Sources/MastodonCore/Service/AuthenticationService.swift +++ b/MastodonSDK/Sources/MastodonCore/Service/AuthenticationService.swift @@ -23,10 +23,8 @@ public final class AuthenticationService: NSObject { let mastodonAuthenticationFetchedResultsController: NSFetchedResultsController // output - public let mastodonAuthentications = CurrentValueSubject<[MastodonAuthentication], Never>([]) - public let mastodonAuthenticationBoxes = CurrentValueSubject<[MastodonAuthenticationBox], Never>([]) - public let activeMastodonAuthentication = CurrentValueSubject(nil) - public let activeMastodonAuthenticationBox = CurrentValueSubject(nil) + @Published public var mastodonAuthentications: [ManagedObjectRecord] = [] + @Published public var mastodonAuthenticationBoxes: [MastodonAuthenticationBox] = [] init( managedObjectContext: NSManagedObjectContext, @@ -53,38 +51,23 @@ public final class AuthenticationService: NSObject { mastodonAuthenticationFetchedResultsController.delegate = self // TODO: verify credentials for active authentication - - // bind data - mastodonAuthentications - .map { $0.sorted(by: { $0.activedAt > $1.activedAt }).first } - .assign(to: \.value, on: activeMastodonAuthentication) - .store(in: &disposeBag) - mastodonAuthentications + $mastodonAuthentications .map { authentications -> [MastodonAuthenticationBox] in return authentications + .compactMap { $0.object(in: managedObjectContext) } .sorted(by: { $0.activedAt > $1.activedAt }) .compactMap { authentication -> MastodonAuthenticationBox? in - return MastodonAuthenticationBox( - authenticationRecord: .init(objectID: authentication.objectID), - domain: authentication.domain, - userID: authentication.userID, - appAuthorization: Mastodon.API.OAuth.Authorization(accessToken: authentication.appAccessToken), - userAuthorization: Mastodon.API.OAuth.Authorization(accessToken: authentication.userAccessToken) - ) + return MastodonAuthenticationBox(authentication: authentication) } } - .assign(to: \.value, on: mastodonAuthenticationBoxes) - .store(in: &disposeBag) - - mastodonAuthenticationBoxes - .map { $0.first } - .assign(to: \.value, on: activeMastodonAuthenticationBox) - .store(in: &disposeBag) - + .assign(to: &$mastodonAuthenticationBoxes) + do { try mastodonAuthenticationFetchedResultsController.performFetch() - mastodonAuthentications.value = mastodonAuthenticationFetchedResultsController.fetchedObjects ?? [] + mastodonAuthentications = mastodonAuthenticationFetchedResultsController.fetchedObjects? + .sorted(by: { $0.activedAt > $1.activedAt }) + .compactMap { $0.asRecrod } ?? [] } catch { assertionFailure(error.localizedDescription) } @@ -94,52 +77,28 @@ public final class AuthenticationService: NSObject { extension AuthenticationService { - public func activeMastodonUser(domain: String, userID: MastodonUser.ID) -> AnyPublisher, Never> { + public func activeMastodonUser(domain: String, userID: MastodonUser.ID) async throws -> Bool { var isActive = false var _mastodonAuthentication: MastodonAuthentication? - return backgroundManagedObjectContext.performChanges { [weak self] in - guard let self = self else { return } - + let managedObjectContext = backgroundManagedObjectContext + + try await managedObjectContext.performChanges { let request = MastodonAuthentication.sortedFetchRequest request.predicate = MastodonAuthentication.predicate(domain: domain, userID: userID) request.fetchLimit = 1 - guard let mastodonAuthentication = try? self.backgroundManagedObjectContext.fetch(request).first else { + guard let mastodonAuthentication = try? managedObjectContext.fetch(request).first else { return } mastodonAuthentication.update(activedAt: Date()) _mastodonAuthentication = mastodonAuthentication isActive = true + } - } - .receive(on: DispatchQueue.main) - .map { [weak self] result in - switch result { - case .success: - if let self = self, - let mastodonAuthentication = _mastodonAuthentication - { - // force set to avoid delay - self.activeMastodonAuthentication.value = mastodonAuthentication - self.activeMastodonAuthenticationBox.value = MastodonAuthenticationBox( - authenticationRecord: .init(objectID: mastodonAuthentication.objectID), - domain: mastodonAuthentication.domain, - userID: mastodonAuthentication.userID, - appAuthorization: Mastodon.API.OAuth.Authorization(accessToken: mastodonAuthentication.appAccessToken), - userAuthorization: Mastodon.API.OAuth.Authorization(accessToken: mastodonAuthentication.userAccessToken) - ) - } - case .failure: - break - } - return result.map { isActive } - } - .eraseToAnyPublisher() + return isActive } - public func signOutMastodonUser( - authenticationBox: MastodonAuthenticationBox - ) async throws { + public func signOutMastodonUser(authenticationBox: MastodonAuthenticationBox) async throws { let managedObjectContext = backgroundManagedObjectContext try await managedObjectContext.performChanges { // remove Feed @@ -176,7 +135,6 @@ extension AuthenticationService { } - // MARK: - NSFetchedResultsControllerDelegate extension AuthenticationService: NSFetchedResultsControllerDelegate { @@ -185,10 +143,14 @@ extension AuthenticationService: NSFetchedResultsControllerDelegate { } public func controllerDidChangeContent(_ controller: NSFetchedResultsController) { - if controller === mastodonAuthenticationFetchedResultsController { - mastodonAuthentications.value = mastodonAuthenticationFetchedResultsController.fetchedObjects ?? [] + guard controller === mastodonAuthenticationFetchedResultsController else { + assertionFailure() + return } + + mastodonAuthentications = mastodonAuthenticationFetchedResultsController.fetchedObjects? + .sorted(by: { $0.activedAt > $1.activedAt }) + .compactMap { $0.asRecrod } ?? [] } } - diff --git a/MastodonSDK/Sources/MastodonCore/Service/BlockDomainService.swift b/MastodonSDK/Sources/MastodonCore/Service/BlockDomainService.swift index c7b2c2333..02b8bdccd 100644 --- a/MastodonSDK/Sources/MastodonCore/Service/BlockDomainService.swift +++ b/MastodonSDK/Sources/MastodonCore/Service/BlockDomainService.swift @@ -14,6 +14,7 @@ import OSLog import UIKit public final class BlockDomainService { + // input weak var backgroundManagedObjectContext: NSManagedObjectContext? weak var authenticationService: AuthenticationService? @@ -27,21 +28,21 @@ public final class BlockDomainService { ) { self.backgroundManagedObjectContext = backgroundManagedObjectContext self.authenticationService = authenticationService - guard let authorizationBox = authenticationService.activeMastodonAuthenticationBox.value else { return } - backgroundManagedObjectContext.perform { - let _blockedDomains: [DomainBlock] = { - let request = DomainBlock.sortedFetchRequest - request.predicate = DomainBlock.predicate(domain: authorizationBox.domain, userID: authorizationBox.userID) - request.returnsObjectsAsFaults = false - do { - return try backgroundManagedObjectContext.fetch(request) - } catch { - assertionFailure(error.localizedDescription) - return [] - } - }() - self.blockedDomains.value = _blockedDomains.map(\.blockedDomain) - } + +// backgroundManagedObjectContext.perform { +// let _blockedDomains: [DomainBlock] = { +// let request = DomainBlock.sortedFetchRequest +// request.predicate = DomainBlock.predicate(domain: authorizationBox.domain, userID: authorizationBox.userID) +// request.returnsObjectsAsFaults = false +// do { +// return try backgroundManagedObjectContext.fetch(request) +// } catch { +// assertionFailure(error.localizedDescription) +// return [] +// } +// }() +// self.blockedDomains.value = _blockedDomains.map(\.blockedDomain) +// } } // func blockDomain( diff --git a/MastodonSDK/Sources/MastodonCore/Service/InstanceService.swift b/MastodonSDK/Sources/MastodonCore/Service/InstanceService.swift index 7632704b0..c63e965bd 100644 --- a/MastodonSDK/Sources/MastodonCore/Service/InstanceService.swift +++ b/MastodonSDK/Sources/MastodonCore/Service/InstanceService.swift @@ -33,9 +33,9 @@ public final class InstanceService { self.apiService = apiService self.authenticationService = authenticationService - authenticationService.activeMastodonAuthenticationBox + authenticationService.$mastodonAuthenticationBoxes .receive(on: DispatchQueue.main) - .compactMap { $0?.domain } + .compactMap { $0.first?.domain } .removeDuplicates() // prevent infinity loop .sink { [weak self] domain in guard let self = self else { return } diff --git a/MastodonSDK/Sources/MastodonCore/Service/Notification/NotificationService.swift b/MastodonSDK/Sources/MastodonCore/Service/Notification/NotificationService.swift index fff84cd4e..18ff2e508 100644 --- a/MastodonSDK/Sources/MastodonCore/Service/Notification/NotificationService.swift +++ b/MastodonSDK/Sources/MastodonCore/Service/Notification/NotificationService.swift @@ -39,7 +39,7 @@ public final class NotificationService { self.apiService = apiService self.authenticationService = authenticationService - authenticationService.mastodonAuthentications + authenticationService.$mastodonAuthentications .sink(receiveValue: { [weak self] mastodonAuthentications in guard let self = self else { return } @@ -61,16 +61,16 @@ public final class NotificationService { .store(in: &disposeBag) Publishers.CombineLatest( - authenticationService.mastodonAuthentications, + authenticationService.$mastodonAuthenticationBoxes, applicationIconBadgeNeedsUpdate ) .receive(on: DispatchQueue.main) - .sink { [weak self] mastodonAuthentications, _ in + .sink { [weak self] mastodonAuthenticationBoxes, _ in guard let self = self else { return } var count = 0 - for authentication in mastodonAuthentications { - count += UserDefaults.shared.getNotificationCountWithAccessToken(accessToken: authentication.userAccessToken) + for authenticationBox in mastodonAuthenticationBoxes { + count += UserDefaults.shared.getNotificationCountWithAccessToken(accessToken: authenticationBox.userAuthorization.accessToken) } UserDefaults.shared.notificationBadgeCount = count @@ -143,7 +143,7 @@ extension NotificationService { extension NotificationService { public func clearNotificationCountForActiveUser() { guard let authenticationService = self.authenticationService else { return } - if let accessToken = authenticationService.activeMastodonAuthentication.value?.userAccessToken { + if let accessToken = authenticationService.mastodonAuthenticationBoxes.first?.userAuthorization.accessToken { UserDefaults.shared.setNotificationCountWithAccessToken(accessToken: accessToken, value: 0) } diff --git a/MastodonSDK/Sources/MastodonCore/Service/SettingService.swift b/MastodonSDK/Sources/MastodonCore/Service/SettingService.swift index b0932450b..48c66baef 100644 --- a/MastodonSDK/Sources/MastodonCore/Service/SettingService.swift +++ b/MastodonSDK/Sources/MastodonCore/Service/SettingService.swift @@ -43,7 +43,7 @@ public final class SettingService { ) // create setting (if non-exist) for authenticated users - authenticationService.mastodonAuthenticationBoxes + authenticationService.$mastodonAuthenticationBoxes .compactMap { [weak self] mastodonAuthenticationBoxes -> AnyPublisher<[MastodonAuthenticationBox], Never>? in guard let self = self else { return nil } guard let authenticationService = self.authenticationService else { return nil } @@ -72,15 +72,15 @@ public final class SettingService { // bind current setting Publishers.CombineLatest( - authenticationService.activeMastodonAuthenticationBox, + authenticationService.$mastodonAuthenticationBoxes, settingFetchedResultController.settings ) - .sink { [weak self] activeMastodonAuthenticationBox, settings in + .sink { [weak self] mastodonAuthenticationBoxes, settings in guard let self = self else { return } - guard let activeMastodonAuthenticationBox = activeMastodonAuthenticationBox else { return } + guard let activeMastodonAuthenticationBox = mastodonAuthenticationBoxes.first else { return } let currentSetting = settings.first(where: { setting in - return setting.domain == activeMastodonAuthenticationBox.domain && - setting.userID == activeMastodonAuthenticationBox.userID + return setting.domain == activeMastodonAuthenticationBox.domain + && setting.userID == activeMastodonAuthenticationBox.userID }) self.currentSetting.value = currentSetting } @@ -114,13 +114,13 @@ public final class SettingService { Publishers.CombineLatest3( notificationService.deviceToken, currentSetting.eraseToAnyPublisher(), - authenticationService.activeMastodonAuthenticationBox + authenticationService.$mastodonAuthenticationBoxes ) - .compactMap { [weak self] deviceToken, setting, activeMastodonAuthenticationBox -> AnyPublisher, Error>? in + .compactMap { [weak self] deviceToken, setting, mastodonAuthenticationBoxes -> AnyPublisher, Error>? in guard let self = self else { return nil } guard let deviceToken = deviceToken else { return nil } guard let setting = setting else { return nil } - guard let authenticationBox = activeMastodonAuthenticationBox else { return nil } + guard let authenticationBox = mastodonAuthenticationBoxes.first else { return nil } guard let subscription = setting.activeSubscription else { return nil } diff --git a/MastodonSDK/Sources/MastodonCore/Service/StatusFilterService.swift b/MastodonSDK/Sources/MastodonCore/Service/StatusFilterService.swift index 92bb10def..e752a022e 100644 --- a/MastodonSDK/Sources/MastodonCore/Service/StatusFilterService.swift +++ b/MastodonSDK/Sources/MastodonCore/Service/StatusFilterService.swift @@ -44,13 +44,12 @@ public final class StatusFilterService { .subscribe(filterUpdatePublisher) .store(in: &disposeBag) - let activeMastodonAuthenticationBox = authenticationService.activeMastodonAuthenticationBox Publishers.CombineLatest( - activeMastodonAuthenticationBox, + authenticationService.$mastodonAuthenticationBoxes, filterUpdatePublisher ) - .flatMap { box, _ -> AnyPublisher, Error>, Never> in - guard let box = box else { + .flatMap { mastodonAuthenticationBoxes, _ -> AnyPublisher, Error>, Never> in + guard let box = mastodonAuthenticationBoxes.first else { return Just(Result { throw APIService.APIError.implicit(.authenticationMissing) }).eraseToAnyPublisher() } return apiService.filters(mastodonAuthenticationBox: box) diff --git a/MastodonSDK/Sources/MastodonUI/View/Content/NotificationView+ViewModel.swift b/MastodonSDK/Sources/MastodonUI/View/Content/NotificationView+ViewModel.swift index 974069801..032760983 100644 --- a/MastodonSDK/Sources/MastodonUI/View/Content/NotificationView+ViewModel.swift +++ b/MastodonSDK/Sources/MastodonUI/View/Content/NotificationView+ViewModel.swift @@ -24,7 +24,7 @@ extension NotificationView { let logger = Logger(subsystem: "NotificationView", category: "ViewModel") - @Published public var userIdentifier: UserIdentifier? // me + @Published public var authContext: AuthContext? @Published public var notificationIndicatorText: MetaContent? @@ -55,11 +55,11 @@ extension NotificationView.ViewModel { bindAuthorMenu(notificationView: notificationView) bindFollowRequest(notificationView: notificationView) - $userIdentifier - .assign(to: \.userIdentifier, on: notificationView.statusView.viewModel) + $authContext + .assign(to: \.authContext, on: notificationView.statusView.viewModel) .store(in: &disposeBag) - $userIdentifier - .assign(to: \.userIdentifier, on: notificationView.quoteStatusView.viewModel) + $authContext + .assign(to: \.authContext, on: notificationView.quoteStatusView.viewModel) .store(in: &disposeBag) } diff --git a/MastodonSDK/Sources/MastodonUI/View/Content/NotificationView.swift b/MastodonSDK/Sources/MastodonUI/View/Content/NotificationView.swift index cacb56a8a..2db731971 100644 --- a/MastodonSDK/Sources/MastodonUI/View/Content/NotificationView.swift +++ b/MastodonSDK/Sources/MastodonUI/View/Content/NotificationView.swift @@ -164,6 +164,8 @@ public final class NotificationView: UIView { disposeBag.removeAll() viewModel.objects.removeAll() + + viewModel.authContext = nil viewModel.authorAvatarImageURL = nil avatarButton.avatarImageView.cancelTask() diff --git a/MastodonSDK/Sources/MastodonUI/View/Content/PollOptionView+ViewModel.swift b/MastodonSDK/Sources/MastodonUI/View/Content/PollOptionView+ViewModel.swift index b6ea11d49..a91f57dc2 100644 --- a/MastodonSDK/Sources/MastodonUI/View/Content/PollOptionView+ViewModel.swift +++ b/MastodonSDK/Sources/MastodonUI/View/Content/PollOptionView+ViewModel.swift @@ -30,7 +30,7 @@ extension PollOptionView { let layoutDidUpdate = PassthroughSubject() - @Published public var userIdentifier: UserIdentifier? + @Published public var authContext: AuthContext? @Published public var style: PollOptionView.Style? diff --git a/MastodonSDK/Sources/MastodonUI/View/Content/StatusView+ViewModel.swift b/MastodonSDK/Sources/MastodonUI/View/Content/StatusView+ViewModel.swift index 648f256b6..cb542da7c 100644 --- a/MastodonSDK/Sources/MastodonUI/View/Content/StatusView+ViewModel.swift +++ b/MastodonSDK/Sources/MastodonUI/View/Content/StatusView+ViewModel.swift @@ -9,14 +9,14 @@ import os.log import UIKit import Combine import CoreData -import Meta -import MastodonSDK -import MastodonCore -import MastodonAsset -import MastodonLocalization -import MastodonExtension -import MastodonCommon import CoreDataStack +import Meta +import MastodonAsset +import MastodonCore +import MastodonCommon +import MastodonExtension +import MastodonLocalization +import MastodonSDK extension StatusView { public final class ViewModel: ObservableObject { @@ -26,7 +26,7 @@ extension StatusView { let logger = Logger(subsystem: "StatusView", category: "ViewModel") - @Published public var userIdentifier: UserIdentifier? // me + public var authContext: AuthContext? // Header @Published public var header: Header = .none @@ -127,6 +127,8 @@ extension StatusView { } public func prepareForReuse() { + authContext = nil + authorAvatarImageURL = nil isContentSensitive = false diff --git a/Podfile.lock b/Podfile.lock index 453610694..0cec6626a 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -37,6 +37,6 @@ SPEC CHECKSUMS: "UITextField+Shake": 298ac5a0f239d731bdab999b19b628c956ca0ac3 XLPagerTabStrip: 61c57fd61f611ee5f01ff1495ad6fbee8bf496c5 -PODFILE CHECKSUM: d95968ab70ea5121c21dfd801aa36b12bcd59c9d +PODFILE CHECKSUM: 50ec5b2c4aa189024cc5ab41039f983dc5609040 COCOAPODS: 1.11.3