From 425d100989f6150e514cf9715e023f027a25bd74 Mon Sep 17 00:00:00 2001 From: jinsu kim Date: Fri, 30 Dec 2022 00:43:19 -0800 Subject: [PATCH 01/13] Fix bug - scrollToTop() should be called when user selects the current tab --- Mastodon/Scene/Root/MainTab/MainTabBarController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mastodon/Scene/Root/MainTab/MainTabBarController.swift b/Mastodon/Scene/Root/MainTab/MainTabBarController.swift index b0af9b6d6..27a9cdf90 100644 --- a/Mastodon/Scene/Root/MainTab/MainTabBarController.swift +++ b/Mastodon/Scene/Root/MainTab/MainTabBarController.swift @@ -572,7 +572,7 @@ extension MainTabBarController: UITabBarControllerDelegate { // Assert index is as same as the tab rawValue. This check needs to be done `shouldSelect` // because the nav controller has already popped in `didSelect`. - if currentTab.rawValue == tabBarController.selectedIndex, + if currentTab.rawValue == viewController.tabBarItem.tag, let navigationController = viewController as? UINavigationController, navigationController.viewControllers.count == 1, let scrollViewContainer = navigationController.topViewController as? ScrollViewContainer { From d1588dda988c01564c6a5d9214db4aed67178cdc Mon Sep 17 00:00:00 2001 From: Rizwan Mohamed Ibrahim Date: Fri, 17 Nov 2023 14:46:00 +0530 Subject: [PATCH 02/13] Add option to focus the search bar when double tapping the search tab bar - Enables double tap for tab bar(s) - Hides double tap for me tab (as per previous comment) - Adds search tab double tab option to focus the search bar --- .../Root/MainTab/MainTabBarController.swift | 36 ++++++++++--------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/Mastodon/Scene/Root/MainTab/MainTabBarController.swift b/Mastodon/Scene/Root/MainTab/MainTabBarController.swift index cd0804b24..bf14825a2 100644 --- a/Mastodon/Scene/Root/MainTab/MainTabBarController.swift +++ b/Mastodon/Scene/Root/MainTab/MainTabBarController.swift @@ -292,12 +292,11 @@ extension MainTabBarController { tabBarLongPressGestureRecognizer.delegate = self tabBar.addGestureRecognizer(tabBarLongPressGestureRecognizer) - // todo: reconsider the "double tap to change account" feature -> https://github.com/mastodon/mastodon-ios/issues/628 -// let tabBarDoubleTapGestureRecognizer = UITapGestureRecognizer() -// tabBarDoubleTapGestureRecognizer.numberOfTapsRequired = 2 -// tabBarDoubleTapGestureRecognizer.addTarget(self, action: #selector(MainTabBarController.tabBarDoubleTapGestureRecognizerHandler(_:))) -// tabBarDoubleTapGestureRecognizer.delaysTouchesEnded = false -// tabBar.addGestureRecognizer(tabBarDoubleTapGestureRecognizer) + let tabBarDoubleTapGestureRecognizer = UITapGestureRecognizer() + tabBarDoubleTapGestureRecognizer.numberOfTapsRequired = 2 + tabBarDoubleTapGestureRecognizer.addTarget(self, action: #selector(MainTabBarController.tabBarDoubleTapGestureRecognizerHandler(_:))) + tabBarDoubleTapGestureRecognizer.delaysTouchesEnded = false + tabBar.addGestureRecognizer(tabBarDoubleTapGestureRecognizer) self.isReadyForWizardAvatarButton = authContext != nil @@ -359,17 +358,22 @@ extension MainTabBarController { guard let tab = touchedTab(by: sender) else { return } switch tab { - case .me: - guard let authContext = authContext else { return } + // todo: reconsider the "double tap to change account" feature -> https://github.com/mastodon/mastodon-ios/issues/628 +// case .me: +// guard let authContext = authContext else { return } +// assert(Thread.isMainThread) +// +// guard let nextAccount = context.nextAccount(in: authContext) else { return } +// +// Task { @MainActor in +// let isActive = try await context.authenticationService.activeMastodonUser(domain: nextAccount.domain, userID: nextAccount.userID) +// guard isActive else { return } +// self.coordinator.setup() +// } + case .search: assert(Thread.isMainThread) - - guard let nextAccount = context.nextAccount(in: authContext) else { return } - - Task { @MainActor in - let isActive = try await context.authenticationService.activeMastodonUser(domain: nextAccount.domain, userID: nextAccount.userID) - guard isActive else { return } - self.coordinator.setup() - } + // double tapping search tab opens the search bar without additional taps + searchViewController?.searchBarTapPublisher.send("") default: break } From 4b298f6bd8baf0df97d912d7baab22ffb4e13c83 Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Fri, 17 Nov 2023 14:48:23 +0100 Subject: [PATCH 03/13] [WIP] Remove Core Data from Suggested Accounts/Familiar Followers (IOS-194) --- Mastodon.xcodeproj/project.pbxproj | 28 +++- .../Provider/DataSourceFacade+Profile.swift | 6 +- .../Discovery/DiscoveryItem.swift | 3 +- .../Discovery/DiscoverySection.swift | 16 +- .../DiscoveryForYouViewController.swift | 35 ++--- .../DiscoveryForYouViewModel+Diffable.swift | 6 +- .../ForYou/DiscoveryForYouViewModel.swift | 42 +++--- ...ofileCardTableViewCell+Configuration.swift | 9 +- .../ForYou}/ProfileCardTableViewCell.swift | 1 + .../ProfileCardView+Configuration.swift | 21 ++- .../ForYou}/ProfileCardView+ViewModel.swift | 137 +++++++++--------- .../Discovery/ForYou}/ProfileCardView.swift | 1 + .../Profile/CachedProfileViewModel.swift | 17 --- .../FamiliarFollowersDashboardView.swift | 12 -- 14 files changed, 155 insertions(+), 179 deletions(-) rename Mastodon/{Diffable => Scene}/Discovery/DiscoveryItem.swift (78%) rename Mastodon/{Diffable => Scene}/Discovery/DiscoverySection.swift (88%) rename {MastodonSDK/Sources/MastodonUI/View/TableViewCell => Mastodon/Scene/Discovery/ForYou}/ProfileCardTableViewCell+Configuration.swift (86%) rename {MastodonSDK/Sources/MastodonUI/View/TableViewCell => Mastodon/Scene/Discovery/ForYou}/ProfileCardTableViewCell.swift (99%) rename {MastodonSDK/Sources/MastodonUI/View/Content => Mastodon/Scene/Discovery/ForYou}/ProfileCardView+Configuration.swift (80%) rename {MastodonSDK/Sources/MastodonUI/View/Content => Mastodon/Scene/Discovery/ForYou}/ProfileCardView+ViewModel.swift (77%) rename {MastodonSDK/Sources/MastodonUI/View/Content => Mastodon/Scene/Discovery/ForYou}/ProfileCardView.swift (99%) delete mode 100644 Mastodon/Scene/Profile/CachedProfileViewModel.swift diff --git a/Mastodon.xcodeproj/project.pbxproj b/Mastodon.xcodeproj/project.pbxproj index 892c6cd57..45d7d3da8 100644 --- a/Mastodon.xcodeproj/project.pbxproj +++ b/Mastodon.xcodeproj/project.pbxproj @@ -137,6 +137,11 @@ D81A22752AB4643200905D71 /* SearchResultsOverviewTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D81A22742AB4643200905D71 /* SearchResultsOverviewTableViewController.swift */; }; D81A22782AB4782400905D71 /* SearchResultOverviewSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = D81A22772AB4782400905D71 /* SearchResultOverviewSection.swift */; }; D81A227B2AB47B9A00905D71 /* SearchResultDefaultSectionTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D81A227A2AB47B9A00905D71 /* SearchResultDefaultSectionTableViewCell.swift */; }; + D81A94122B07A1BE0067A19D /* ProfileCardTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D81A94112B07A1BE0067A19D /* ProfileCardTableViewCell.swift */; }; + D81A94132B07A1BE0067A19D /* ProfileCardTableViewCell+Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D81A94102B07A1BE0067A19D /* ProfileCardTableViewCell+Configuration.swift */; }; + D81A94172B07A1D30067A19D /* ProfileCardView+Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D81A94162B07A1D30067A19D /* ProfileCardView+Configuration.swift */; }; + D81A94182B07A1D30067A19D /* ProfileCardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D81A94142B07A1D30067A19D /* ProfileCardView.swift */; }; + D81A94192B07A1D30067A19D /* ProfileCardView+ViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D81A94152B07A1D30067A19D /* ProfileCardView+ViewModel.swift */; }; D81D12462A4E1861005009D4 /* PolicySelectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D81D12452A4E1861005009D4 /* PolicySelectionViewController.swift */; }; D81D124B2A4E1914005009D4 /* ToggleTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D81D124A2A4E1914005009D4 /* ToggleTableViewCell.swift */; }; D82BD7532ABC44C2009A374A /* Coordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8F9170E2A4B47EF008A5370 /* Coordinator.swift */; }; @@ -451,7 +456,6 @@ DBCBCBF4267CB070000F5B51 /* Decode85.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBCBCBF3267CB070000F5B51 /* Decode85.swift */; }; DBCBED1726132DB500B49291 /* UserTimelineViewModel+Diffable.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBCBED1626132DB500B49291 /* UserTimelineViewModel+Diffable.swift */; }; DBCC3B30261440A50045B23D /* UITabBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBCC3B2F261440A50045B23D /* UITabBarController.swift */; }; - DBCC3B8F26148F7B0045B23D /* CachedProfileViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBCC3B8E26148F7B0045B23D /* CachedProfileViewModel.swift */; }; DBD376B2269302A4007FEC24 /* UITableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBD376B1269302A4007FEC24 /* UITableViewCell.swift */; }; DBD5B1F827BCFD9D00BD6B38 /* DataSourceProvider+TableViewControllerNavigateable.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBD5B1F727BCFD9D00BD6B38 /* DataSourceProvider+TableViewControllerNavigateable.swift */; }; DBD5B1FA27BD013700BD6B38 /* DataSourceProvider+StatusTableViewControllerNavigateable.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBD5B1F927BD013700BD6B38 /* DataSourceProvider+StatusTableViewControllerNavigateable.swift */; }; @@ -794,6 +798,11 @@ D81A940D2B04E7AC0067A19D /* hy */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hy; path = hy.lproj/LaunchScreen.strings; sourceTree = ""; }; D81A940E2B04E7AD0067A19D /* hy */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hy; path = hy.lproj/MainInterface.strings; sourceTree = ""; }; D81A940F2B04E7AD0067A19D /* hy */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hy; path = hy.lproj/InfoPlist.strings; sourceTree = ""; }; + D81A94102B07A1BE0067A19D /* ProfileCardTableViewCell+Configuration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProfileCardTableViewCell+Configuration.swift"; sourceTree = ""; }; + D81A94112B07A1BE0067A19D /* ProfileCardTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileCardTableViewCell.swift; sourceTree = ""; }; + D81A94142B07A1D30067A19D /* ProfileCardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileCardView.swift; sourceTree = ""; }; + D81A94152B07A1D30067A19D /* ProfileCardView+ViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProfileCardView+ViewModel.swift"; sourceTree = ""; }; + D81A94162B07A1D30067A19D /* ProfileCardView+Configuration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProfileCardView+Configuration.swift"; sourceTree = ""; }; D81D12452A4E1861005009D4 /* PolicySelectionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PolicySelectionViewController.swift; sourceTree = ""; }; D81D124A2A4E1914005009D4 /* ToggleTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToggleTableViewCell.swift; sourceTree = ""; }; D82463522A52B47B00A3DBDD /* be */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = be; path = be.lproj/Intents.strings; sourceTree = ""; }; @@ -1194,7 +1203,6 @@ DBCBCBF3267CB070000F5B51 /* Decode85.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Decode85.swift; sourceTree = ""; }; DBCBED1626132DB500B49291 /* UserTimelineViewModel+Diffable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UserTimelineViewModel+Diffable.swift"; sourceTree = ""; }; DBCC3B2F261440A50045B23D /* UITabBarController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITabBarController.swift; sourceTree = ""; }; - DBCC3B8E26148F7B0045B23D /* CachedProfileViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CachedProfileViewModel.swift; sourceTree = ""; }; DBD376B1269302A4007FEC24 /* UITableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITableViewCell.swift; sourceTree = ""; }; DBD5B1F727BCFD9D00BD6B38 /* DataSourceProvider+TableViewControllerNavigateable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DataSourceProvider+TableViewControllerNavigateable.swift"; sourceTree = ""; }; DBD5B1F927BD013700BD6B38 /* DataSourceProvider+StatusTableViewControllerNavigateable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DataSourceProvider+StatusTableViewControllerNavigateable.swift"; sourceTree = ""; }; @@ -2094,8 +2102,6 @@ DB3E6FE52806A5BA00B035AE /* Discovery */ = { isa = PBXGroup; children = ( - DB3E6FE32806A5B800B035AE /* DiscoverySection.swift */, - DB3E6FE62806A7A200B035AE /* DiscoveryItem.swift */, ); path = Discovery; sourceTree = ""; @@ -2114,6 +2120,11 @@ DB3E6FF62807C40500B035AE /* ForYou */ = { isa = PBXGroup; children = ( + D81A94142B07A1D30067A19D /* ProfileCardView.swift */, + D81A94162B07A1D30067A19D /* ProfileCardView+Configuration.swift */, + D81A94152B07A1D30067A19D /* ProfileCardView+ViewModel.swift */, + D81A94112B07A1BE0067A19D /* ProfileCardTableViewCell.swift */, + D81A94102B07A1BE0067A19D /* ProfileCardTableViewCell+Configuration.swift */, DB3E6FF42807C40300B035AE /* DiscoveryForYouViewController.swift */, DB3E6FF72807C45300B035AE /* DiscoveryForYouViewModel.swift */, DB3E6FF92807C47900B035AE /* DiscoveryForYouViewModel+Diffable.swift */, @@ -2763,7 +2774,6 @@ DBFEEC97279BDC6A004F81DD /* About */, DB9D6BFE25E4F5940051B173 /* ProfileViewController.swift */, DBB5255D2611F07A002F1F29 /* ProfileViewModel.swift */, - DBCC3B8E26148F7B0045B23D /* CachedProfileViewModel.swift */, DBAE3FAE26172FC0004B8251 /* RemoteProfileViewModel.swift */, DBB525632612C988002F1F29 /* MeProfileViewModel.swift */, ); @@ -2926,6 +2936,8 @@ DB3E6FED2806D7FC00B035AE /* News */, DB3EA8E7281B79E500598866 /* Community */, DB3E6FF62807C40500B035AE /* ForYou */, + DB3E6FE32806A5B800B035AE /* DiscoverySection.swift */, + DB3E6FE62806A7A200B035AE /* DiscoveryItem.swift */, DBDFF19928055A1400557A48 /* DiscoveryViewController.swift */, DBDFF19B28055BD600557A48 /* DiscoveryViewModel.swift */, ); @@ -3727,6 +3739,7 @@ 2D38F1EB25CD477000561493 /* HomeTimelineViewModel+LoadLatestState.swift in Sources */, DB5B7295273112B100081888 /* FollowingListViewController.swift in Sources */, 0F202201261326E6000C64BF /* HashtagTimelineViewModel.swift in Sources */, + D81A94172B07A1D30067A19D /* ProfileCardView+Configuration.swift in Sources */, DB63F7452799056400455B82 /* HashtagTableViewCell.swift in Sources */, DBAE3FAF26172FC0004B8251 /* RemoteProfileViewModel.swift in Sources */, D82BD7552ABC73AF009A374A /* NotificationPolicyTableViewCell.swift in Sources */, @@ -3772,6 +3785,7 @@ 2A506CF4292CD85800059C37 /* FollowedTagsViewController.swift in Sources */, DB1D843026566512000346B3 /* KeyboardPreference.swift in Sources */, DB852D1926FAEB6B00FC9D81 /* SidebarViewController.swift in Sources */, + D81A94132B07A1BE0067A19D /* ProfileCardTableViewCell+Configuration.swift in Sources */, 2D206B9225F60EA700143C56 /* UIControl.swift in Sources */, 85904C02293BC0EB0011C817 /* ImageProvider.swift in Sources */, DBDFF1932805554900557A48 /* DiscoveryPostsViewModel.swift in Sources */, @@ -3799,6 +3813,7 @@ DB63F769279A5EBB00455B82 /* NotificationTimelineViewModel+Diffable.swift in Sources */, DBFEEC9B279BDDD9004F81DD /* ProfileAboutViewModel+Diffable.swift in Sources */, DBB525562611EDCA002F1F29 /* UserTimelineViewModel.swift in Sources */, + D81A94192B07A1D30067A19D /* ProfileCardView+ViewModel.swift in Sources */, D8916DC029211BE500124085 /* ContentSizedTableView.swift in Sources */, DB0618012785732C0030EE79 /* ServerRulesTableViewCell.swift in Sources */, DB98EB5C27B10A730082E365 /* ReportSupplementaryViewModel.swift in Sources */, @@ -3875,7 +3890,6 @@ 2D38F20825CD491300561493 /* DisposeBagCollectable.swift in Sources */, D8F9170D2A4B3C6F008A5370 /* AboutMastodonTableViewCell.swift in Sources */, DB697DE1278F5296004EF2F7 /* DataSourceFacade+Model.swift in Sources */, - DBCC3B8F26148F7B0045B23D /* CachedProfileViewModel.swift in Sources */, DB4F097526A037F500D62E92 /* SearchHistoryViewModel.swift in Sources */, DB3EA8E9281B7A3700598866 /* DiscoveryCommunityViewModel.swift in Sources */, D87BFC8B291D5C6B00FEE264 /* MastodonLoginView.swift in Sources */, @@ -3974,6 +3988,7 @@ DBEFCD7D282A2A3B00C0ABEA /* ReportServerRulesViewController.swift in Sources */, DBB525362611ECEB002F1F29 /* UserTimelineViewController.swift in Sources */, D8F917122A4C6B67008A5370 /* GeneralSettingsViewController.swift in Sources */, + D81A94122B07A1BE0067A19D /* ProfileCardTableViewCell.swift in Sources */, DB98EB4927B0F0CD0082E365 /* ReportStatusTableViewCell.swift in Sources */, 855149CA29606D6400943D96 /* PortraitAlertController.swift in Sources */, DBF3B7412733EB9400E21627 /* MastodonLocalCode.swift in Sources */, @@ -3981,6 +3996,7 @@ DB4F096A269EDAD200D62E92 /* SearchResultViewModel+State.swift in Sources */, D8ECC8102AC31EA400AE0818 /* NotificationSettingsDisabledTableViewCell.swift in Sources */, 5BB04FF5262F0E6D0043BFF6 /* ReportSection.swift in Sources */, + D81A94182B07A1D30067A19D /* ProfileCardView.swift in Sources */, DBEFCD82282A2AB100C0ABEA /* ReportServerRulesView.swift in Sources */, DBA94436265CBB7400C537E1 /* ProfileFieldItem.swift in Sources */, 2A3F6FE5292F6E44002E6DA7 /* FollowedTagsTableViewCell.swift in Sources */, diff --git a/Mastodon/Protocol/Provider/DataSourceFacade+Profile.swift b/Mastodon/Protocol/Provider/DataSourceFacade+Profile.swift index 30c024f54..c34b62704 100644 --- a/Mastodon/Protocol/Provider/DataSourceFacade+Profile.swift +++ b/Mastodon/Protocol/Provider/DataSourceFacade+Profile.swift @@ -42,10 +42,10 @@ extension DataSourceFacade { return } - let profileViewModel = CachedProfileViewModel( + let profileViewModel = ProfileViewModel( context: provider.context, authContext: provider.authContext, - mastodonUser: user + optionalMastodonUser: user ) _ = provider.coordinator.present( @@ -125,7 +125,7 @@ extension DataSourceFacade { let _user = provider.context.managedObjectContext.safeFetch(request).first if let user = _user { - return CachedProfileViewModel(context: provider.context, authContext: provider.authContext, mastodonUser: user) + return ProfileViewModel(context: provider.context, authContext: provider.authContext, optionalMastodonUser: user) } else { return RemoteProfileViewModel(context: provider.context, authContext: provider.authContext, userID: userID) } diff --git a/Mastodon/Diffable/Discovery/DiscoveryItem.swift b/Mastodon/Scene/Discovery/DiscoveryItem.swift similarity index 78% rename from Mastodon/Diffable/Discovery/DiscoveryItem.swift rename to Mastodon/Scene/Discovery/DiscoveryItem.swift index 024c4a2da..0d9a69a4e 100644 --- a/Mastodon/Diffable/Discovery/DiscoveryItem.swift +++ b/Mastodon/Scene/Discovery/DiscoveryItem.swift @@ -7,11 +7,10 @@ import Foundation import MastodonSDK -import CoreDataStack enum DiscoveryItem: Hashable { case hashtag(Mastodon.Entity.Tag) case link(Mastodon.Entity.Link) - case user(ManagedObjectRecord) + case account(Mastodon.Entity.Account) case bottomLoader } diff --git a/Mastodon/Diffable/Discovery/DiscoverySection.swift b/Mastodon/Scene/Discovery/DiscoverySection.swift similarity index 88% rename from Mastodon/Diffable/Discovery/DiscoverySection.swift rename to Mastodon/Scene/Discovery/DiscoverySection.swift index bb93ffc28..fd028fbd1 100644 --- a/Mastodon/Diffable/Discovery/DiscoverySection.swift +++ b/Mastodon/Scene/Discovery/DiscoverySection.swift @@ -55,19 +55,15 @@ extension DiscoverySection { let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: NewsTableViewCell.self), for: indexPath) as! NewsTableViewCell cell.newsView.configure(link: link) return cell - case .user(let record): + case .account(let account): let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: ProfileCardTableViewCell.self), for: indexPath) as! ProfileCardTableViewCell - context.managedObjectContext.performAndWait { - guard let user = record.object(in: context.managedObjectContext) else { return } - cell.configure( - tableView: tableView, - user: user, - profileCardTableViewCellDelegate: configuration.profileCardTableViewCellDelegate - ) + + cell.configure(tableView: tableView, account: account, profileCardTableViewCellDelegate: configuration.profileCardTableViewCellDelegate) + // bind familiarFollowers if let familiarFollowers = configuration.familiarFollowers { familiarFollowers - .map { array in array.first(where: { $0.id == user.id }) } + .map { array in array.first(where: { $0.id == account.id }) } .assign(to: \.familiarFollowers, on: cell.profileCardView.viewModel) .store(in: &cell.disposeBag) } else { @@ -75,7 +71,7 @@ extension DiscoverySection { } // bind me cell.profileCardView.viewModel.relationshipViewModel.me = configuration.authContext.mastodonAuthenticationBox.authentication.user(in: context.managedObjectContext) - } +// } return cell case .bottomLoader: let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: TimelineBottomLoaderTableViewCell.self), for: indexPath) as! TimelineBottomLoaderTableViewCell diff --git a/Mastodon/Scene/Discovery/ForYou/DiscoveryForYouViewController.swift b/Mastodon/Scene/Discovery/ForYou/DiscoveryForYouViewController.swift index 952ba4937..6e5cbcb15 100644 --- a/Mastodon/Scene/Discovery/ForYou/DiscoveryForYouViewController.swift +++ b/Mastodon/Scene/Discovery/ForYou/DiscoveryForYouViewController.swift @@ -96,18 +96,11 @@ extension DiscoveryForYouViewController: AuthContextProvider { extension DiscoveryForYouViewController: UITableViewDelegate { func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - guard case let .user(record) = viewModel.diffableDataSource?.itemIdentifier(for: indexPath) else { return } - guard let user = record.object(in: context.managedObjectContext) else { return } - let profileViewModel = CachedProfileViewModel( - context: context, - authContext: viewModel.authContext, - mastodonUser: user - ) - _ = coordinator.present( - scene: .profile(viewModel: profileViewModel), - from: self, - transition: .show - ) + guard case let .account(account) = viewModel.diffableDataSource?.itemIdentifier(for: indexPath) else { return } + + Task { + await DataSourceFacade.coordinateToProfileScene(provider: self, account: account) + } } } @@ -120,14 +113,11 @@ extension DiscoveryForYouViewController: ProfileCardTableViewCellDelegate { relationshipButtonDidPressed button: ProfileRelationshipActionButton ) { guard let indexPath = tableView.indexPath(for: cell) else { return } - guard case let .user(record) = viewModel.diffableDataSource?.itemIdentifier(for: indexPath) else { return } - + guard case let .account(account) = viewModel.diffableDataSource?.itemIdentifier(for: indexPath) else { return } + Task { - try await DataSourceFacade.responseToUserFollowAction( - dependency: self, - user: record - ) - } // end Task + try await DataSourceFacade.responseToUserFollowAction(dependency: self, user: account) + } } func profileCardTableViewCell( @@ -136,10 +126,9 @@ extension DiscoveryForYouViewController: ProfileCardTableViewCellDelegate { familiarFollowersDashboardViewDidPressed view: FamiliarFollowersDashboardView ) { guard let indexPath = tableView.indexPath(for: cell) else { return } - guard case let .user(record) = viewModel.diffableDataSource?.itemIdentifier(for: indexPath) else { return } - guard let user = record.object(in: context.managedObjectContext) else { return } - - let userID = user.id + guard case let .account(account) = viewModel.diffableDataSource?.itemIdentifier(for: indexPath) else { return } + + let userID = account.id let _familiarFollowers = viewModel.familiarFollowers.first(where: { $0.id == userID }) guard let familiarFollowers = _familiarFollowers else { assertionFailure() diff --git a/Mastodon/Scene/Discovery/ForYou/DiscoveryForYouViewModel+Diffable.swift b/Mastodon/Scene/Discovery/ForYou/DiscoveryForYouViewModel+Diffable.swift index 31b14c55d..c4ca3abae 100644 --- a/Mastodon/Scene/Discovery/ForYou/DiscoveryForYouViewModel+Diffable.swift +++ b/Mastodon/Scene/Discovery/ForYou/DiscoveryForYouViewModel+Diffable.swift @@ -29,16 +29,16 @@ extension DiscoveryForYouViewModel { try await fetch() } - userFetchedResultsController.$records + $accounts .receive(on: DispatchQueue.main) - .sink { [weak self] records in + .sink { [weak self] accounts in guard let self = self else { return } guard let diffableDataSource = self.diffableDataSource else { return } var snapshot = NSDiffableDataSourceSnapshot() snapshot.appendSections([.forYou]) - let items = records.map { DiscoveryItem.user($0) } + let items = accounts.map { DiscoveryItem.account($0) } snapshot.appendItems(items, toSection: .forYou) diffableDataSource.apply(snapshot, animatingDifferences: false) diff --git a/Mastodon/Scene/Discovery/ForYou/DiscoveryForYouViewModel.swift b/Mastodon/Scene/Discovery/ForYou/DiscoveryForYouViewModel.swift index fcd521a36..53549209d 100644 --- a/Mastodon/Scene/Discovery/ForYou/DiscoveryForYouViewModel.swift +++ b/Mastodon/Scene/Discovery/ForYou/DiscoveryForYouViewModel.swift @@ -8,8 +8,6 @@ import UIKit import Combine import GameplayKit -import CoreData -import CoreDataStack import MastodonSDK import MastodonCore @@ -20,11 +18,11 @@ final class DiscoveryForYouViewModel { // input let context: AppContext let authContext: AuthContext - let userFetchedResultsController: UserFetchedResultsController - + @MainActor @Published var familiarFollowers: [Mastodon.Entity.FamiliarFollowers] = [] @Published var isFetching = false + @Published var accounts: [Mastodon.Entity.Account] // output var diffableDataSource: UITableViewDiffableDataSource? @@ -33,12 +31,7 @@ final class DiscoveryForYouViewModel { init(context: AppContext, authContext: AuthContext) { self.context = context self.authContext = authContext - self.userFetchedResultsController = UserFetchedResultsController( - managedObjectContext: context.managedObjectContext, - domain: authContext.mastodonAuthenticationBox.domain, - additionalPredicate: nil - ) - // end init + self.accounts = [] } } @@ -46,40 +39,39 @@ extension DiscoveryForYouViewModel { @MainActor func fetch() async throws { - guard !isFetching else { return } + guard isFetching == false else { return } isFetching = true defer { isFetching = false } do { - let userIDs = try await fetchSuggestionAccounts() - - let _familiarFollowersResponse = try? await context.apiService.familiarFollowers( - query: .init(ids: userIDs), + let suggestedAccounts = try await fetchSuggestionAccounts() + + let familiarFollowersResponse = try? await context.apiService.familiarFollowers( + query: .init(ids: suggestedAccounts.compactMap { $0.id }), authenticationBox: authContext.mastodonAuthenticationBox - ) - familiarFollowers = _familiarFollowersResponse?.value ?? [] - userFetchedResultsController.userIDs = userIDs + ).value + familiarFollowers = familiarFollowersResponse ?? [] + accounts = suggestedAccounts } catch { // do nothing } } - private func fetchSuggestionAccounts() async throws -> [Mastodon.Entity.Account.ID] { + private func fetchSuggestionAccounts() async throws -> [Mastodon.Entity.Account] { do { let response = try await context.apiService.suggestionAccountV2( query: nil, authenticationBox: authContext.mastodonAuthenticationBox - ) - let userIDs = response.value.map { $0.account.id } - return userIDs + ).value + return response.compactMap { $0.account } } catch { // fallback V1 let response = try await context.apiService.suggestionAccount( query: nil, authenticationBox: authContext.mastodonAuthenticationBox - ) - let userIDs = response.value.map { $0.id } - return userIDs + ).value + + return response } } } diff --git a/MastodonSDK/Sources/MastodonUI/View/TableViewCell/ProfileCardTableViewCell+Configuration.swift b/Mastodon/Scene/Discovery/ForYou/ProfileCardTableViewCell+Configuration.swift similarity index 86% rename from MastodonSDK/Sources/MastodonUI/View/TableViewCell/ProfileCardTableViewCell+Configuration.swift rename to Mastodon/Scene/Discovery/ForYou/ProfileCardTableViewCell+Configuration.swift index d4767048a..4a50e611c 100644 --- a/MastodonSDK/Sources/MastodonUI/View/TableViewCell/ProfileCardTableViewCell+Configuration.swift +++ b/Mastodon/Scene/Discovery/ForYou/ProfileCardTableViewCell+Configuration.swift @@ -6,14 +6,13 @@ // import UIKit -import CoreDataStack import MastodonSDK extension ProfileCardTableViewCell { public func configure( tableView: UITableView, - user: MastodonUser, + account: Mastodon.Entity.Account, profileCardTableViewCellDelegate: ProfileCardTableViewCellDelegate? ) { if profileCardView.frame == .zero { @@ -22,9 +21,9 @@ extension ProfileCardTableViewCell { shadowBackgroundContainer.frame.size.width = layoutMarginsGuide.layoutFrame.width profileCardView.setupLayoutFrame(layoutMarginsGuide.layoutFrame) } - - profileCardView.configure(user: user) + + profileCardView.configure(account: account) delegate = profileCardTableViewCellDelegate } - + } diff --git a/MastodonSDK/Sources/MastodonUI/View/TableViewCell/ProfileCardTableViewCell.swift b/Mastodon/Scene/Discovery/ForYou/ProfileCardTableViewCell.swift similarity index 99% rename from MastodonSDK/Sources/MastodonUI/View/TableViewCell/ProfileCardTableViewCell.swift rename to Mastodon/Scene/Discovery/ForYou/ProfileCardTableViewCell.swift index 3b3227e23..f12369437 100644 --- a/MastodonSDK/Sources/MastodonUI/View/TableViewCell/ProfileCardTableViewCell.swift +++ b/Mastodon/Scene/Discovery/ForYou/ProfileCardTableViewCell.swift @@ -7,6 +7,7 @@ import UIKit import Combine +import MastodonUI public protocol ProfileCardTableViewCellDelegate: AnyObject { func profileCardTableViewCell(_ cell: ProfileCardTableViewCell, profileCardView: ProfileCardView, relationshipButtonDidPressed button: ProfileRelationshipActionButton) diff --git a/MastodonSDK/Sources/MastodonUI/View/Content/ProfileCardView+Configuration.swift b/Mastodon/Scene/Discovery/ForYou/ProfileCardView+Configuration.swift similarity index 80% rename from MastodonSDK/Sources/MastodonUI/View/Content/ProfileCardView+Configuration.swift rename to Mastodon/Scene/Discovery/ForYou/ProfileCardView+Configuration.swift index fcf83e75b..3db4698f7 100644 --- a/MastodonSDK/Sources/MastodonUI/View/Content/ProfileCardView+Configuration.swift +++ b/Mastodon/Scene/Discovery/ForYou/ProfileCardView+Configuration.swift @@ -15,12 +15,23 @@ import MastodonSDK extension ProfileCardView { + public func configure(account: Mastodon.Entity.Account) { + //TODO: Implement + viewModel.authorBannerImageURL = URL(string: account.header) + + do { + let content = MastodonContent(content: account.displayNameWithFallback, emojis: account.emojis?.asDictionary ?? [:]) + let metaContent = try MastodonMetaContent.convert(document: content) + viewModel.authorName = metaContent + } catch { + assertionFailure(error.localizedDescription) + let metaContent = PlaintextMetaContent(string: account.displayNameWithFallback) + viewModel.authorName = metaContent + } + + } + public func configure(user: MastodonUser) { - // banner - user.publisher(for: \.header) - .map { URL(string: $0) } - .assign(to: \.authorBannerImageURL, on: viewModel) - .store(in: &disposeBag) // author avatar Publishers.CombineLatest3( user.publisher(for: \.avatar), diff --git a/MastodonSDK/Sources/MastodonUI/View/Content/ProfileCardView+ViewModel.swift b/Mastodon/Scene/Discovery/ForYou/ProfileCardView+ViewModel.swift similarity index 77% rename from MastodonSDK/Sources/MastodonUI/View/Content/ProfileCardView+ViewModel.swift rename to Mastodon/Scene/Discovery/ForYou/ProfileCardView+ViewModel.swift index 25b8fc653..f375ee478 100644 --- a/MastodonSDK/Sources/MastodonUI/View/Content/ProfileCardView+ViewModel.swift +++ b/Mastodon/Scene/Discovery/ForYou/ProfileCardView+ViewModel.swift @@ -14,6 +14,7 @@ import MastodonLocalization import MastodonAsset import MastodonSDK import MastodonCore +import MastodonUI extension ProfileCardView { public class ViewModel: ObservableObject { @@ -77,7 +78,7 @@ extension ProfileCardView.ViewModel { bindRelationship(view: view) bindDashboard(view: view) bindFamiliarFollowers(view: view) - bindAccessibility(view: view) +// bindAccessibility(view: view) } private func bindAppearacne(view: ProfileCardView) { @@ -212,73 +213,73 @@ extension ProfileCardView.ViewModel { view.familiarFollowersDashboardView.configure(familiarFollowers: familiarFollowers) } .store(in: &disposeBag) - $backgroundColor - .assign(to: \.backgroundColor, on: view.familiarFollowersDashboardView.viewModel) - .store(in: &disposeBag) +// $backgroundColor +// .assign(to: \.backgroundColor, on: view.familiarFollowersDashboardView.viewModel) +// .store(in: &disposeBag) } - private func bindAccessibility(view: ProfileCardView) { - let authorAccessibilityLabel = Publishers.CombineLatest( - $authorName, - $bioContent - ) - .map { authorName, bioContent -> String? in - var strings: [String?] = [] - strings.append(authorName?.string) - strings.append(bioContent?.string) - return strings.compactMap { $0 }.joined(separator: ", ") - } - - authorAccessibilityLabel - .map { $0 ?? "" } - .assign(to: &$groupedAccessibilityLabel) - - $groupedAccessibilityLabel - .sink { accessibilityLabel in - view.accessibilityLabel = accessibilityLabel - } - .store(in: &disposeBag) - - let statusesContent = $statusesCount - .removeDuplicates() - .map { - AXCustomContent( - label: L10n.Scene.Profile.Dashboard.otherPosts, - value: $0 - ) - } - let followingContent = $followingCount - .removeDuplicates() - .map { - AXCustomContent( - label: L10n.Scene.Profile.Dashboard.otherFollowing, - value: $0 - ) - } - let followersContent = $followersCount - .removeDuplicates() - .map { - AXCustomContent( - label: L10n.Scene.Profile.Dashboard.otherFollowers, - value: $0 - ) - } - let familiarContent = view.familiarFollowersDashboardView.viewModel.$label - .map { $0?.accessibilityLabel } - .removeDuplicates() - .map { - AXCustomContent( - label: L10n.Scene.Profile.Dashboard.familiarFollowers, - value: $0 - ) - } - Publishers.CombineLatest4( - statusesContent, - followingContent, - followersContent, - familiarContent - ).sink { statuses, following, followers, familiar in - view.accessibilityCustomContent = [statuses, following, followers, familiar].compactMap { $0 } - }.store(in: &disposeBag) - } +// private func bindAccessibility(view: ProfileCardView) { +// let authorAccessibilityLabel = Publishers.CombineLatest( +// $authorName, +// $bioContent +// ) +// .map { authorName, bioContent -> String? in +// var strings: [String?] = [] +// strings.append(authorName?.string) +// strings.append(bioContent?.string) +// return strings.compactMap { $0 }.joined(separator: ", ") +// } +// +// authorAccessibilityLabel +// .map { $0 ?? "" } +// .assign(to: &$groupedAccessibilityLabel) +// +// $groupedAccessibilityLabel +// .sink { accessibilityLabel in +// view.accessibilityLabel = accessibilityLabel +// } +// .store(in: &disposeBag) +// +// let statusesContent = $statusesCount +// .removeDuplicates() +// .map { +// AXCustomContent( +// label: L10n.Scene.Profile.Dashboard.otherPosts, +// value: "\($0.)" +// ) +// } +// let followingContent = $followingCount +// .removeDuplicates() +// .map { +// AXCustomContent( +// label: L10n.Scene.Profile.Dashboard.otherFollowing, +// value: $0 +// ) +// } +// let followersContent = $followersCount +// .removeDuplicates() +// .map { +// AXCustomContent( +// label: L10n.Scene.Profile.Dashboard.otherFollowers, +// value: $0 +// ) +// } +// let familiarContent = view.familiarFollowersDashboardView.viewModel.$label +// .map { $0?.accessibilityLabel } +// .removeDuplicates() +// .map { +// AXCustomContent( +// label: L10n.Scene.Profile.Dashboard.familiarFollowers, +// value: $0 +// ) +// } +// Publishers.CombineLatest4( +// statusesContent, +// followingContent, +// followersContent, +// familiarContent +// ).sink { statuses, following, followers, familiar in +// view.accessibilityCustomContent = [statuses, following, followers, familiar].compactMap { $0 } +// }.store(in: &disposeBag) +// } } diff --git a/MastodonSDK/Sources/MastodonUI/View/Content/ProfileCardView.swift b/Mastodon/Scene/Discovery/ForYou/ProfileCardView.swift similarity index 99% rename from MastodonSDK/Sources/MastodonUI/View/Content/ProfileCardView.swift rename to Mastodon/Scene/Discovery/ForYou/ProfileCardView.swift index ad8762c71..1407c745c 100644 --- a/MastodonSDK/Sources/MastodonUI/View/Content/ProfileCardView.swift +++ b/Mastodon/Scene/Discovery/ForYou/ProfileCardView.swift @@ -9,6 +9,7 @@ import UIKit import Combine import MetaTextKit import MastodonAsset +import MastodonUI public protocol ProfileCardViewDelegate: AnyObject { func profileCardView(_ profileCardView: ProfileCardView, relationshipButtonDidPressed button: ProfileRelationshipActionButton) diff --git a/Mastodon/Scene/Profile/CachedProfileViewModel.swift b/Mastodon/Scene/Profile/CachedProfileViewModel.swift deleted file mode 100644 index a769f2a9f..000000000 --- a/Mastodon/Scene/Profile/CachedProfileViewModel.swift +++ /dev/null @@ -1,17 +0,0 @@ -// -// CachedProfileViewModel.swift -// Mastodon -// -// Created by MainasuK Cirno on 2021-3-31. -// - -import Foundation -import CoreDataStack -import MastodonCore - -final class CachedProfileViewModel: ProfileViewModel { - - init(context: AppContext, authContext: AuthContext, mastodonUser: MastodonUser) { - super.init(context: context, authContext: authContext, optionalMastodonUser: mastodonUser) - } -} diff --git a/MastodonSDK/Sources/MastodonUI/View/Content/FamiliarFollowersDashboardView.swift b/MastodonSDK/Sources/MastodonUI/View/Content/FamiliarFollowersDashboardView.swift index 32d9bce96..caf85483f 100644 --- a/MastodonSDK/Sources/MastodonUI/View/Content/FamiliarFollowersDashboardView.swift +++ b/MastodonSDK/Sources/MastodonUI/View/Content/FamiliarFollowersDashboardView.swift @@ -63,15 +63,3 @@ extension FamiliarFollowersDashboardView { } } - - -#if DEBUG -import SwiftUI -struct FamiliarFollowersDashboardView_Preview: PreviewProvider { - static var previews: some View { - UIViewPreview { - FamiliarFollowersDashboardView() - } - } -} -#endif From bbebfac99de1bae5826401d0dd9bf1817f280fad Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Mon, 20 Nov 2023 12:13:08 +0100 Subject: [PATCH 04/13] Remove obsolete folder (IOS-194) --- Mastodon.xcodeproj/project.pbxproj | 8 -------- 1 file changed, 8 deletions(-) diff --git a/Mastodon.xcodeproj/project.pbxproj b/Mastodon.xcodeproj/project.pbxproj index 45d7d3da8..58de81668 100644 --- a/Mastodon.xcodeproj/project.pbxproj +++ b/Mastodon.xcodeproj/project.pbxproj @@ -1658,7 +1658,6 @@ DB65C63527A2AF52008BAC2E /* Report */, DB0617F727855B010030EE79 /* Notification */, DB4F097726A039A200D62E92 /* Search */, - DB3E6FE52806A5BA00B035AE /* Discovery */, ); path = Diffable; sourceTree = ""; @@ -2099,13 +2098,6 @@ path = Hashtags; sourceTree = ""; }; - DB3E6FE52806A5BA00B035AE /* Discovery */ = { - isa = PBXGroup; - children = ( - ); - path = Discovery; - sourceTree = ""; - }; DB3E6FED2806D7FC00B035AE /* News */ = { isa = PBXGroup; children = ( From c45252b294a3293f5f7d7814a149638113fd638d Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Mon, 20 Nov 2023 12:27:10 +0100 Subject: [PATCH 05/13] Set properties based on Entity.Account (IOS-194) --- .../ProfileCardView+Configuration.swift | 86 ++++--------------- 1 file changed, 17 insertions(+), 69 deletions(-) diff --git a/Mastodon/Scene/Discovery/ForYou/ProfileCardView+Configuration.swift b/Mastodon/Scene/Discovery/ForYou/ProfileCardView+Configuration.swift index 3db4698f7..1e7a0f5ed 100644 --- a/Mastodon/Scene/Discovery/ForYou/ProfileCardView+Configuration.swift +++ b/Mastodon/Scene/Discovery/ForYou/ProfileCardView+Configuration.swift @@ -16,11 +16,16 @@ import MastodonSDK extension ProfileCardView { public func configure(account: Mastodon.Entity.Account) { - //TODO: Implement viewModel.authorBannerImageURL = URL(string: account.header) - + viewModel.statusesCount = account.statusesCount + viewModel.followingCount = account.followingCount + viewModel.followersCount = account.followersCount + viewModel.authorAvatarImageURL = account.avatarImageURL() + + let emojis = account.emojis?.asDictionary ?? [:] + do { - let content = MastodonContent(content: account.displayNameWithFallback, emojis: account.emojis?.asDictionary ?? [:]) + let content = MastodonContent(content: account.displayNameWithFallback, emojis: emojis) let metaContent = try MastodonMetaContent.convert(document: content) viewModel.authorName = metaContent } catch { @@ -29,73 +34,16 @@ extension ProfileCardView { viewModel.authorName = metaContent } - } + viewModel.authorUsername = account.acct - public func configure(user: MastodonUser) { - // author avatar - Publishers.CombineLatest3( - user.publisher(for: \.avatar), - user.publisher(for: \.avatarStatic), - UserDefaults.shared.publisher(for: \.preferredStaticAvatar) - ) - .map { _ in user.avatarImageURL() } - .assign(to: \.authorAvatarImageURL, on: viewModel) - .store(in: &disposeBag) - // name - Publishers.CombineLatest( - user.publisher(for: \.displayName), - user.publisher(for: \.emojis) - ) - .map { _, emojis in - do { - let content = MastodonContent(content: user.displayNameWithFallback, emojis: emojis.asDictionary) - let metaContent = try MastodonMetaContent.convert(document: content) - return metaContent - } catch { - assertionFailure(error.localizedDescription) - return PlaintextMetaContent(string: user.displayNameWithFallback) - } + do { + let content = MastodonContent(content: account.note, emojis: emojis) + let metaContent = try MastodonMetaContent.convert(document: content) + viewModel.bioContent = metaContent + } catch { + assertionFailure(error.localizedDescription) + let metaContent = PlaintextMetaContent(string: account.note) + viewModel.bioContent = metaContent } - .assign(to: \.authorName, on: viewModel) - .store(in: &disposeBag) - // username - user.publisher(for: \.acct) - .map { $0 as String? } - .assign(to: \.authorUsername, on: viewModel) - .store(in: &disposeBag) - // bio - Publishers.CombineLatest( - user.publisher(for: \.note), - user.publisher(for: \.emojis) - ) - .map { note, emojis in - guard let note = note else { return nil } - do { - let content = MastodonContent(content: note, emojis: emojis.asDictionary) - let metaContent = try MastodonMetaContent.convert(document: content) - return metaContent - } catch { - assertionFailure(error.localizedDescription) - return nil - } - } - .assign(to: \.bioContent, on: viewModel) - .store(in: &disposeBag) - // relationship - viewModel.relationshipViewModel.user = user - // dashboard - user.publisher(for: \.statusesCount) - .map { Int($0) } - .assign(to: \.statusesCount, on: viewModel) - .store(in: &disposeBag) - user.publisher(for: \.followingCount) - .map { Int($0) } - .assign(to: \.followingCount, on: viewModel) - .store(in: &disposeBag) - user.publisher(for: \.followersCount) - .map { Int($0) } - .assign(to: \.followersCount, on: viewModel) - .store(in: &disposeBag) } - } From 32520be6c9ec3da52931a5b9592bc28fa5bffd22 Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Mon, 20 Nov 2023 12:35:13 +0100 Subject: [PATCH 06/13] Fix background color (IOS-194) --- .../ForYou/ProfileCardView+ViewModel.swift | 36 ------------------- .../Discovery/ForYou/ProfileCardView.swift | 8 ++--- .../Contents.json | 6 ++-- 3 files changed, 5 insertions(+), 45 deletions(-) diff --git a/Mastodon/Scene/Discovery/ForYou/ProfileCardView+ViewModel.swift b/Mastodon/Scene/Discovery/ForYou/ProfileCardView+ViewModel.swift index f375ee478..b9ce95d35 100644 --- a/Mastodon/Scene/Discovery/ForYou/ProfileCardView+ViewModel.swift +++ b/Mastodon/Scene/Discovery/ForYou/ProfileCardView+ViewModel.swift @@ -21,9 +21,6 @@ extension ProfileCardView { var disposeBag = Set() public let relationshipViewModel = RelationshipViewModel() - - @Published public var userInterfaceStyle: UIUserInterfaceStyle? - @Published public var backgroundColor: UIColor? // Author @Published public var authorBannerImageURL: URL? @@ -46,32 +43,11 @@ extension ProfileCardView { @Published public var groupedAccessibilityLabel = "" @Published public var familiarFollowers: Mastodon.Entity.FamiliarFollowers? - - init() { - backgroundColor = .systemBackground - $userInterfaceStyle - .sink { [weak self] userInterfaceStyle in - guard let self = self else { return } - guard let userInterfaceStyle = userInterfaceStyle else { return } - switch userInterfaceStyle { - case .dark: - self.backgroundColor = .secondarySystemBackground - case .light, .unspecified: - self.backgroundColor = Asset.Scene.Discovery.profileCardBackground.color - @unknown default: - self.backgroundColor = Asset.Scene.Discovery.profileCardBackground.color - assertionFailure() - // do nothing - } - } - .store(in: &disposeBag) - } } } extension ProfileCardView.ViewModel { func bind(view: ProfileCardView) { - bindAppearacne(view: view) bindHeader(view: view) bindUser(view: view) bindBio(view: view) @@ -81,18 +57,6 @@ extension ProfileCardView.ViewModel { // bindAccessibility(view: view) } - private func bindAppearacne(view: ProfileCardView) { - userInterfaceStyle = view.traitCollection.userInterfaceStyle - - $backgroundColor - .assign(to: \.backgroundColor, on: view.container) - .store(in: &disposeBag) - $backgroundColor - .assign(to: \.backgroundColor, on: view.avatarButtonBackgroundView) - .store(in: &disposeBag) - } - - private func bindHeader(view: ProfileCardView) { $authorBannerImageURL .sink { url in diff --git a/Mastodon/Scene/Discovery/ForYou/ProfileCardView.swift b/Mastodon/Scene/Discovery/ForYou/ProfileCardView.swift index 1407c745c..7a2cadb2a 100644 --- a/Mastodon/Scene/Discovery/ForYou/ProfileCardView.swift +++ b/Mastodon/Scene/Discovery/ForYou/ProfileCardView.swift @@ -136,6 +136,7 @@ extension ProfileCardView { container.axis = .vertical container.spacing = 8 container.translatesAutoresizingMaskIntoConstraints = false + container.backgroundColor = Asset.Scene.Discovery.profileCardBackground.color addSubview(container) container.pinToParent() @@ -184,6 +185,7 @@ extension ProfileCardView { avatarButton.heightAnchor.constraint(equalToConstant: ProfileCardView.avatarSize.height).priority(.required - 1), ]) + avatarButtonBackgroundView.backgroundColor = Asset.Scene.Discovery.profileCardBackground.color avatarButtonBackgroundView.layer.masksToBounds = true avatarButtonBackgroundView.layer.cornerCurve = .continuous avatarButtonBackgroundView.layer.cornerRadius = 12 + 1 @@ -256,12 +258,6 @@ extension ProfileCardView { familiarFollowersDashboardView.addGestureRecognizer(familiarFollowersDashboardViewTapGestureRecognizer) } - public override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { - super.traitCollectionDidChange(previousTraitCollection) - - viewModel.userInterfaceStyle = traitCollection.userInterfaceStyle - } - public override func layoutSubviews() { updateInfoContainerLayout() super.layoutSubviews() diff --git a/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Discovery/profile.card.background.colorset/Contents.json b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Discovery/profile.card.background.colorset/Contents.json index 1fc21a878..9c8c3063a 100644 --- a/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Discovery/profile.card.background.colorset/Contents.json +++ b/MastodonSDK/Sources/MastodonAsset/Assets.xcassets/Scene/Discovery/profile.card.background.colorset/Contents.json @@ -23,9 +23,9 @@ "color-space" : "srgb", "components" : { "alpha" : "1.000", - "blue" : "4", - "green" : "5", - "red" : "6" + "blue" : "0x1E", + "green" : "0x1C", + "red" : "0x1C" } }, "idiom" : "universal" From 873c5befe2220b8c081f565ddebd52f13262e92c Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Mon, 20 Nov 2023 13:00:37 +0100 Subject: [PATCH 07/13] Re-enable A11y (IOS-194) --- .../ForYou/ProfileCardView+ViewModel.swift | 134 +++++++++--------- ...liarFollowersDashboardView+ViewModel.swift | 2 +- 2 files changed, 66 insertions(+), 70 deletions(-) diff --git a/Mastodon/Scene/Discovery/ForYou/ProfileCardView+ViewModel.swift b/Mastodon/Scene/Discovery/ForYou/ProfileCardView+ViewModel.swift index b9ce95d35..8f830b47a 100644 --- a/Mastodon/Scene/Discovery/ForYou/ProfileCardView+ViewModel.swift +++ b/Mastodon/Scene/Discovery/ForYou/ProfileCardView+ViewModel.swift @@ -54,7 +54,7 @@ extension ProfileCardView.ViewModel { bindRelationship(view: view) bindDashboard(view: view) bindFamiliarFollowers(view: view) -// bindAccessibility(view: view) + bindAccessibility(view: view) } private func bindHeader(view: ProfileCardView) { @@ -177,73 +177,69 @@ extension ProfileCardView.ViewModel { view.familiarFollowersDashboardView.configure(familiarFollowers: familiarFollowers) } .store(in: &disposeBag) -// $backgroundColor -// .assign(to: \.backgroundColor, on: view.familiarFollowersDashboardView.viewModel) -// .store(in: &disposeBag) } - -// private func bindAccessibility(view: ProfileCardView) { -// let authorAccessibilityLabel = Publishers.CombineLatest( -// $authorName, -// $bioContent -// ) -// .map { authorName, bioContent -> String? in -// var strings: [String?] = [] -// strings.append(authorName?.string) -// strings.append(bioContent?.string) -// return strings.compactMap { $0 }.joined(separator: ", ") -// } -// -// authorAccessibilityLabel -// .map { $0 ?? "" } -// .assign(to: &$groupedAccessibilityLabel) -// -// $groupedAccessibilityLabel -// .sink { accessibilityLabel in -// view.accessibilityLabel = accessibilityLabel -// } -// .store(in: &disposeBag) -// -// let statusesContent = $statusesCount -// .removeDuplicates() -// .map { -// AXCustomContent( -// label: L10n.Scene.Profile.Dashboard.otherPosts, -// value: "\($0.)" -// ) -// } -// let followingContent = $followingCount -// .removeDuplicates() -// .map { -// AXCustomContent( -// label: L10n.Scene.Profile.Dashboard.otherFollowing, -// value: $0 -// ) -// } -// let followersContent = $followersCount -// .removeDuplicates() -// .map { -// AXCustomContent( -// label: L10n.Scene.Profile.Dashboard.otherFollowers, -// value: $0 -// ) -// } -// let familiarContent = view.familiarFollowersDashboardView.viewModel.$label -// .map { $0?.accessibilityLabel } -// .removeDuplicates() -// .map { -// AXCustomContent( -// label: L10n.Scene.Profile.Dashboard.familiarFollowers, -// value: $0 -// ) -// } -// Publishers.CombineLatest4( -// statusesContent, -// followingContent, -// followersContent, -// familiarContent -// ).sink { statuses, following, followers, familiar in -// view.accessibilityCustomContent = [statuses, following, followers, familiar].compactMap { $0 } -// }.store(in: &disposeBag) -// } + private func bindAccessibility(view: ProfileCardView) { + let authorAccessibilityLabel = Publishers.CombineLatest( + $authorName, + $bioContent + ) + .map { authorName, bioContent -> String? in + var strings: [String?] = [] + strings.append(authorName?.string) + strings.append(bioContent?.string) + return strings.compactMap { $0 }.joined(separator: ", ") + } + + authorAccessibilityLabel + .map { $0 ?? "" } + .assign(to: &$groupedAccessibilityLabel) + + $groupedAccessibilityLabel + .sink { accessibilityLabel in + view.accessibilityLabel = accessibilityLabel + } + .store(in: &disposeBag) + + let statusesContent = $statusesCount + .removeDuplicates() + .map { + AXCustomContent( + label: L10n.Scene.Profile.Dashboard.otherPosts, + value: String(describing: $0) + ) + } + let followingContent = $followingCount + .removeDuplicates() + .map { + AXCustomContent( + label: L10n.Scene.Profile.Dashboard.otherFollowing, + value: String(describing: $0) + ) + } + let followersContent = $followersCount + .removeDuplicates() + .map { + AXCustomContent( + label: L10n.Scene.Profile.Dashboard.otherFollowers, + value: String(describing: $0) + ) + } + let familiarContent = view.familiarFollowersDashboardView.viewModel.$label + .map { $0?.accessibilityLabel ?? ""} + .removeDuplicates() + .map { + AXCustomContent( + label: L10n.Scene.Profile.Dashboard.familiarFollowers, + value: $0 + ) + } + Publishers.CombineLatest4( + statusesContent, + followingContent, + followersContent, + familiarContent + ).sink { statuses, following, followers, familiar in + view.accessibilityCustomContent = [statuses, following, followers, familiar].compactMap { $0 } + }.store(in: &disposeBag) + } } diff --git a/MastodonSDK/Sources/MastodonUI/View/Content/FamiliarFollowersDashboardView+ViewModel.swift b/MastodonSDK/Sources/MastodonUI/View/Content/FamiliarFollowersDashboardView+ViewModel.swift index cd4871414..2db0a5966 100644 --- a/MastodonSDK/Sources/MastodonUI/View/Content/FamiliarFollowersDashboardView+ViewModel.swift +++ b/MastodonSDK/Sources/MastodonUI/View/Content/FamiliarFollowersDashboardView+ViewModel.swift @@ -22,7 +22,7 @@ extension FamiliarFollowersDashboardView { @Published var emojis: MastodonContent.Emojis = [:] @Published var backgroundColor: UIColor? - @Published var label: MetaContent? + @Published public var label: MetaContent? } } From 601d52c28f0fe11437ff459cf3db859fef32881d Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Mon, 20 Nov 2023 13:51:20 +0100 Subject: [PATCH 08/13] Consider relationship and replace follow-button (IOS-194) --- Mastodon/Scene/Discovery/DiscoveryItem.swift | 2 +- .../Scene/Discovery/DiscoverySection.swift | 55 +++-- .../DiscoveryForYouViewController.swift | 8 +- .../DiscoveryForYouViewModel+Diffable.swift | 17 -- .../ForYou/DiscoveryForYouViewModel.swift | 26 +++ ...ofileCardTableViewCell+Configuration.swift | 3 +- .../ForYou/ProfileCardTableViewCell.swift | 6 +- .../ProfileCardView+Configuration.swift | 4 +- .../ForYou/ProfileCardView+ViewModel.swift | 26 --- .../Discovery/ForYou/ProfileCardView.swift | 215 +++++++++++++----- 10 files changed, 233 insertions(+), 129 deletions(-) diff --git a/Mastodon/Scene/Discovery/DiscoveryItem.swift b/Mastodon/Scene/Discovery/DiscoveryItem.swift index 0d9a69a4e..face3429d 100644 --- a/Mastodon/Scene/Discovery/DiscoveryItem.swift +++ b/Mastodon/Scene/Discovery/DiscoveryItem.swift @@ -11,6 +11,6 @@ import MastodonSDK enum DiscoveryItem: Hashable { case hashtag(Mastodon.Entity.Tag) case link(Mastodon.Entity.Link) - case account(Mastodon.Entity.Account) + case account(Mastodon.Entity.Account, relationship: Mastodon.Entity.Relationship?) case bottomLoader } diff --git a/Mastodon/Scene/Discovery/DiscoverySection.swift b/Mastodon/Scene/Discovery/DiscoverySection.swift index fd028fbd1..e4c809631 100644 --- a/Mastodon/Scene/Discovery/DiscoverySection.swift +++ b/Mastodon/Scene/Discovery/DiscoverySection.swift @@ -18,12 +18,12 @@ enum DiscoverySection: CaseIterable { } extension DiscoverySection { - + class Configuration { let authContext: AuthContext weak var profileCardTableViewCellDelegate: ProfileCardTableViewCellDelegate? let familiarFollowers: Published<[Mastodon.Entity.FamiliarFollowers]>.Publisher? - + public init( authContext: AuthContext, profileCardTableViewCellDelegate: ProfileCardTableViewCellDelegate? = nil, @@ -34,31 +34,40 @@ extension DiscoverySection { self.familiarFollowers = familiarFollowers } } - + static func diffableDataSource( tableView: UITableView, context: AppContext, configuration: Configuration ) -> UITableViewDiffableDataSource { + tableView.register(TrendTableViewCell.self, forCellReuseIdentifier: String(describing: TrendTableViewCell.self)) tableView.register(NewsTableViewCell.self, forCellReuseIdentifier: String(describing: NewsTableViewCell.self)) tableView.register(ProfileCardTableViewCell.self, forCellReuseIdentifier: String(describing: ProfileCardTableViewCell.self)) tableView.register(TimelineBottomLoaderTableViewCell.self, forCellReuseIdentifier: String(describing: TimelineBottomLoaderTableViewCell.self)) - return UITableViewDiffableDataSource(tableView: tableView) { tableView, indexPath, item in + return UITableViewDiffableDataSource(tableView: tableView) { + tableView, + indexPath, + item in switch item { - case .hashtag(let tag): - let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: TrendTableViewCell.self), for: indexPath) as! TrendTableViewCell - cell.trendView.configure(tag: tag) - return cell - case .link(let link): - let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: NewsTableViewCell.self), for: indexPath) as! NewsTableViewCell - cell.newsView.configure(link: link) - return cell - case .account(let account): - let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: ProfileCardTableViewCell.self), for: indexPath) as! ProfileCardTableViewCell + case .hashtag(let tag): + let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: TrendTableViewCell.self), for: indexPath) as! TrendTableViewCell + cell.trendView.configure(tag: tag) + return cell + case .link(let link): + let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: NewsTableViewCell.self), for: indexPath) as! NewsTableViewCell + cell.newsView.configure(link: link) + return cell + case .account(let account, relationship: let relationship): + let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: ProfileCardTableViewCell.self), for: indexPath) as! ProfileCardTableViewCell - cell.configure(tableView: tableView, account: account, profileCardTableViewCellDelegate: configuration.profileCardTableViewCellDelegate) + cell.configure( + tableView: tableView, + account: account, + relationship: relationship, + profileCardTableViewCellDelegate: configuration.profileCardTableViewCellDelegate + ) // bind familiarFollowers if let familiarFollowers = configuration.familiarFollowers { @@ -69,16 +78,14 @@ extension DiscoverySection { } else { cell.profileCardView.viewModel.familiarFollowers = nil } - // bind me - cell.profileCardView.viewModel.relationshipViewModel.me = configuration.authContext.mastodonAuthenticationBox.authentication.user(in: context.managedObjectContext) -// } - return cell - case .bottomLoader: - let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: TimelineBottomLoaderTableViewCell.self), for: indexPath) as! TimelineBottomLoaderTableViewCell - cell.activityIndicatorView.startAnimating() - return cell + + return cell + case .bottomLoader: + let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: TimelineBottomLoaderTableViewCell.self), for: indexPath) as! TimelineBottomLoaderTableViewCell + cell.activityIndicatorView.startAnimating() + return cell } } } - + } diff --git a/Mastodon/Scene/Discovery/ForYou/DiscoveryForYouViewController.swift b/Mastodon/Scene/Discovery/ForYou/DiscoveryForYouViewController.swift index 6e5cbcb15..8d1b947be 100644 --- a/Mastodon/Scene/Discovery/ForYou/DiscoveryForYouViewController.swift +++ b/Mastodon/Scene/Discovery/ForYou/DiscoveryForYouViewController.swift @@ -96,7 +96,7 @@ extension DiscoveryForYouViewController: AuthContextProvider { extension DiscoveryForYouViewController: UITableViewDelegate { func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - guard case let .account(account) = viewModel.diffableDataSource?.itemIdentifier(for: indexPath) else { return } + guard case let .account(account, _) = viewModel.diffableDataSource?.itemIdentifier(for: indexPath) else { return } Task { await DataSourceFacade.coordinateToProfileScene(provider: self, account: account) @@ -110,10 +110,10 @@ extension DiscoveryForYouViewController: ProfileCardTableViewCellDelegate { func profileCardTableViewCell( _ cell: ProfileCardTableViewCell, profileCardView: ProfileCardView, - relationshipButtonDidPressed button: ProfileRelationshipActionButton + relationshipButtonDidPressed button: UIButton ) { guard let indexPath = tableView.indexPath(for: cell) else { return } - guard case let .account(account) = viewModel.diffableDataSource?.itemIdentifier(for: indexPath) else { return } + guard case let .account(account, _) = viewModel.diffableDataSource?.itemIdentifier(for: indexPath) else { return } Task { try await DataSourceFacade.responseToUserFollowAction(dependency: self, user: account) @@ -126,7 +126,7 @@ extension DiscoveryForYouViewController: ProfileCardTableViewCellDelegate { familiarFollowersDashboardViewDidPressed view: FamiliarFollowersDashboardView ) { guard let indexPath = tableView.indexPath(for: cell) else { return } - guard case let .account(account) = viewModel.diffableDataSource?.itemIdentifier(for: indexPath) else { return } + guard case let .account(account, _) = viewModel.diffableDataSource?.itemIdentifier(for: indexPath) else { return } let userID = account.id let _familiarFollowers = viewModel.familiarFollowers.first(where: { $0.id == userID }) diff --git a/Mastodon/Scene/Discovery/ForYou/DiscoveryForYouViewModel+Diffable.swift b/Mastodon/Scene/Discovery/ForYou/DiscoveryForYouViewModel+Diffable.swift index c4ca3abae..d516c17fe 100644 --- a/Mastodon/Scene/Discovery/ForYou/DiscoveryForYouViewModel+Diffable.swift +++ b/Mastodon/Scene/Discovery/ForYou/DiscoveryForYouViewModel+Diffable.swift @@ -28,22 +28,5 @@ extension DiscoveryForYouViewModel { Task { try await fetch() } - - $accounts - .receive(on: DispatchQueue.main) - .sink { [weak self] accounts in - guard let self = self else { return } - guard let diffableDataSource = self.diffableDataSource else { return } - - var snapshot = NSDiffableDataSourceSnapshot() - snapshot.appendSections([.forYou]) - - let items = accounts.map { DiscoveryItem.account($0) } - snapshot.appendItems(items, toSection: .forYou) - - diffableDataSource.apply(snapshot, animatingDifferences: false) - } - .store(in: &disposeBag) } - } diff --git a/Mastodon/Scene/Discovery/ForYou/DiscoveryForYouViewModel.swift b/Mastodon/Scene/Discovery/ForYou/DiscoveryForYouViewModel.swift index 53549209d..4f0f730f4 100644 --- a/Mastodon/Scene/Discovery/ForYou/DiscoveryForYouViewModel.swift +++ b/Mastodon/Scene/Discovery/ForYou/DiscoveryForYouViewModel.swift @@ -23,6 +23,7 @@ final class DiscoveryForYouViewModel { @Published var familiarFollowers: [Mastodon.Entity.FamiliarFollowers] = [] @Published var isFetching = false @Published var accounts: [Mastodon.Entity.Account] + var relationships: [Mastodon.Entity.Relationship?] // output var diffableDataSource: UITableViewDiffableDataSource? @@ -32,6 +33,7 @@ final class DiscoveryForYouViewModel { self.context = context self.authContext = authContext self.accounts = [] + self.relationships = [] } } @@ -50,11 +52,35 @@ extension DiscoveryForYouViewModel { query: .init(ids: suggestedAccounts.compactMap { $0.id }), authenticationBox: authContext.mastodonAuthenticationBox ).value + + let relationships = try? await context.apiService.relationship( + forAccounts: suggestedAccounts, + authenticationBox: authContext.mastodonAuthenticationBox + ).value + familiarFollowers = familiarFollowersResponse ?? [] accounts = suggestedAccounts + self.relationships = relationships ?? [] } catch { // do nothing } + + await MainActor.run { + guard let diffableDataSource = self.diffableDataSource else { return } + + var snapshot = NSDiffableDataSourceSnapshot() + snapshot.appendSections([.forYou]) + + let items = self.accounts.map { account in + let relationship = relationships.first { $0?.id == account.id } ?? nil + + return DiscoveryItem.account(account, relationship: relationship) + } + + snapshot.appendItems(items, toSection: .forYou) + + diffableDataSource.apply(snapshot, animatingDifferences: false) + } } private func fetchSuggestionAccounts() async throws -> [Mastodon.Entity.Account] { diff --git a/Mastodon/Scene/Discovery/ForYou/ProfileCardTableViewCell+Configuration.swift b/Mastodon/Scene/Discovery/ForYou/ProfileCardTableViewCell+Configuration.swift index 4a50e611c..f472602e1 100644 --- a/Mastodon/Scene/Discovery/ForYou/ProfileCardTableViewCell+Configuration.swift +++ b/Mastodon/Scene/Discovery/ForYou/ProfileCardTableViewCell+Configuration.swift @@ -13,6 +13,7 @@ extension ProfileCardTableViewCell { public func configure( tableView: UITableView, account: Mastodon.Entity.Account, + relationship: Mastodon.Entity.Relationship?, profileCardTableViewCellDelegate: ProfileCardTableViewCellDelegate? ) { if profileCardView.frame == .zero { @@ -22,7 +23,7 @@ extension ProfileCardTableViewCell { profileCardView.setupLayoutFrame(layoutMarginsGuide.layoutFrame) } - profileCardView.configure(account: account) + profileCardView.configure(account: account, relationship: relationship) delegate = profileCardTableViewCellDelegate } diff --git a/Mastodon/Scene/Discovery/ForYou/ProfileCardTableViewCell.swift b/Mastodon/Scene/Discovery/ForYou/ProfileCardTableViewCell.swift index f12369437..322071fad 100644 --- a/Mastodon/Scene/Discovery/ForYou/ProfileCardTableViewCell.swift +++ b/Mastodon/Scene/Discovery/ForYou/ProfileCardTableViewCell.swift @@ -10,7 +10,7 @@ import Combine import MastodonUI public protocol ProfileCardTableViewCellDelegate: AnyObject { - func profileCardTableViewCell(_ cell: ProfileCardTableViewCell, profileCardView: ProfileCardView, relationshipButtonDidPressed button: ProfileRelationshipActionButton) + func profileCardTableViewCell(_ cell: ProfileCardTableViewCell, profileCardView: ProfileCardView, relationshipButtonDidPressed button: UIButton) func profileCardTableViewCell(_ cell: ProfileCardTableViewCell, profileCardView: ProfileCardView, familiarFollowersDashboardViewDidPressed view: FamiliarFollowersDashboardView) } @@ -72,7 +72,7 @@ extension ProfileCardTableViewCell { profileCardView.isAccessibilityElement = true accessibilityElements = [ profileCardView, - profileCardView.relationshipActionButton + profileCardView.followButton ] } @@ -81,7 +81,7 @@ extension ProfileCardTableViewCell { // MARK: - ProfileCardViewDelegate extension ProfileCardTableViewCell: ProfileCardViewDelegate { - public func profileCardView(_ profileCardView: ProfileCardView, relationshipButtonDidPressed button: ProfileRelationshipActionButton) { + public func profileCardView(_ profileCardView: ProfileCardView, relationshipButtonDidPressed button: UIButton) { delegate?.profileCardTableViewCell(self, profileCardView: profileCardView, relationshipButtonDidPressed: button) } diff --git a/Mastodon/Scene/Discovery/ForYou/ProfileCardView+Configuration.swift b/Mastodon/Scene/Discovery/ForYou/ProfileCardView+Configuration.swift index 1e7a0f5ed..4192e59b7 100644 --- a/Mastodon/Scene/Discovery/ForYou/ProfileCardView+Configuration.swift +++ b/Mastodon/Scene/Discovery/ForYou/ProfileCardView+Configuration.swift @@ -15,7 +15,7 @@ import MastodonSDK extension ProfileCardView { - public func configure(account: Mastodon.Entity.Account) { + public func configure(account: Mastodon.Entity.Account, relationship: Mastodon.Entity.Relationship?) { viewModel.authorBannerImageURL = URL(string: account.header) viewModel.statusesCount = account.statusesCount viewModel.followingCount = account.followingCount @@ -45,5 +45,7 @@ extension ProfileCardView { let metaContent = PlaintextMetaContent(string: account.note) viewModel.bioContent = metaContent } + + updateButtonState(with: relationship, isMe: false) } } diff --git a/Mastodon/Scene/Discovery/ForYou/ProfileCardView+ViewModel.swift b/Mastodon/Scene/Discovery/ForYou/ProfileCardView+ViewModel.swift index 8f830b47a..e7bf776c7 100644 --- a/Mastodon/Scene/Discovery/ForYou/ProfileCardView+ViewModel.swift +++ b/Mastodon/Scene/Discovery/ForYou/ProfileCardView+ViewModel.swift @@ -20,8 +20,6 @@ extension ProfileCardView { public class ViewModel: ObservableObject { var disposeBag = Set() - public let relationshipViewModel = RelationshipViewModel() - // Author @Published public var authorBannerImageURL: URL? @Published public var authorAvatarImageURL: URL? @@ -51,7 +49,6 @@ extension ProfileCardView.ViewModel { bindHeader(view: view) bindUser(view: view) bindBio(view: view) - bindRelationship(view: view) bindDashboard(view: view) bindFamiliarFollowers(view: view) bindAccessibility(view: view) @@ -117,30 +114,7 @@ extension ProfileCardView.ViewModel { .store(in: &disposeBag) } - private func bindRelationship(view: ProfileCardView) { - relationshipViewModel.$optionSet - .receive(on: DispatchQueue.main) - .sink { relationshipActionSet in - let relationshipActionSet = relationshipActionSet ?? .follow - view.relationshipActionButton.configure(actionOptionSet: relationshipActionSet) - } - .store(in: &disposeBag) - } - private func bindDashboard(view: ProfileCardView) { - relationshipViewModel.$isMyself - .sink { isMyself in - if isMyself { - view.statusDashboardView.postDashboardMeterView.textLabel.text = L10n.Scene.Profile.Dashboard.myPosts - view.statusDashboardView.followingDashboardMeterView.textLabel.text = L10n.Scene.Profile.Dashboard.myFollowing - view.statusDashboardView.followersDashboardMeterView.textLabel.text = L10n.Scene.Profile.Dashboard.myFollowers - } else { - view.statusDashboardView.postDashboardMeterView.textLabel.text = L10n.Scene.Profile.Dashboard.otherPosts - view.statusDashboardView.followingDashboardMeterView.textLabel.text = L10n.Scene.Profile.Dashboard.otherFollowing - view.statusDashboardView.followersDashboardMeterView.textLabel.text = L10n.Scene.Profile.Dashboard.otherFollowers - } - } - .store(in: &disposeBag) $statusesCount .receive(on: DispatchQueue.main) .sink { count in diff --git a/Mastodon/Scene/Discovery/ForYou/ProfileCardView.swift b/Mastodon/Scene/Discovery/ForYou/ProfileCardView.swift index 7a2cadb2a..6dcdc6f49 100644 --- a/Mastodon/Scene/Discovery/ForYou/ProfileCardView.swift +++ b/Mastodon/Scene/Discovery/ForYou/ProfileCardView.swift @@ -1,6 +1,6 @@ // // ProfileCardView.swift -// +// // // Created by MainasuK on 2022-4-14. // @@ -10,14 +10,16 @@ import Combine import MetaTextKit import MastodonAsset import MastodonUI +import MastodonLocalization +import MastodonSDK public protocol ProfileCardViewDelegate: AnyObject { - func profileCardView(_ profileCardView: ProfileCardView, relationshipButtonDidPressed button: ProfileRelationshipActionButton) + func profileCardView(_ profileCardView: ProfileCardView, relationshipButtonDidPressed button: UIButton) func profileCardView(_ profileCardView: ProfileCardView, familiarFollowersDashboardViewDidPressed view: FamiliarFollowersDashboardView) } public final class ProfileCardView: UIView, AXCustomContentProvider { - + static let avatarSize = CGSize(width: 56, height: 56) static let friendshipActionButtonSize = CGSize(width: 108, height: 34) static let contentMargin: CGFloat = 16 @@ -29,7 +31,7 @@ public final class ProfileCardView: UIView, AXCustomContentProvider { public var accessibilityCustomContent: [AXCustomContent]! = [] let container = UIStackView() - + let bannerImageView: UIImageView = { let imageView = UIImageView() imageView.contentMode = .scaleAspectFill @@ -38,17 +40,17 @@ public final class ProfileCardView: UIView, AXCustomContentProvider { imageView.layer.cornerCurve = .continuous return imageView }() - + // avatar public let avatarButtonBackgroundView = UIView() public let avatarButton = AvatarButton() - + // author name public let authorNameLabel = MetaLabel(style: .profileCardName) - + // author username public let authorUsernameLabel = MetaLabel(style: .profileCardUsername) - + // bio let bioMetaTextAdaptiveMarginContainerView = AdaptiveMarginContainerView() let bioMetaText: MetaText = { @@ -82,46 +84,56 @@ public final class ProfileCardView: UIView, AXCustomContentProvider { ] return metaText }() - + let infoContainerAdaptiveMarginContainerView = AdaptiveMarginContainerView() let infoContainer = UIStackView() - + let statusDashboardView = ProfileStatusDashboardView() - - let relationshipActionButtonShadowContainer = ShadowBackgroundContainer() - let relationshipActionButton: ProfileRelationshipActionButton = { - let button = ProfileRelationshipActionButton() - button.titleLabel?.font = .systemFont(ofSize: 17, weight: .semibold) - button.titleLabel?.adjustsFontSizeToFitWidth = true - button.titleLabel?.minimumScaleFactor = 0.5 + + public let followButtonWrapper = UIView() + public let followButton: UIButton = { + var buttonConfiguration = UIButton.Configuration.filled() + buttonConfiguration.background.cornerRadius = 10 + + let button = UIButton(configuration: buttonConfiguration) + button.isHidden = true + button.translatesAutoresizingMaskIntoConstraints = false + button.setContentCompressionResistancePriority(.required, for: .horizontal) + button.setContentHuggingPriority(.required, for: .horizontal) + + NSLayoutConstraint.activate([ + button.widthAnchor.constraint(equalToConstant: 96), + button.heightAnchor.constraint(equalToConstant: 36) + ]) + return button }() - + let familiarFollowersDashboardViewAdaptiveMarginContainerView = AdaptiveMarginContainerView() let familiarFollowersDashboardView = FamiliarFollowersDashboardView() - + public private(set) lazy var viewModel: ViewModel = { let viewModel = ViewModel() viewModel.bind(view: self) return viewModel }() - + public func prepareForReuse() { disposeBag.removeAll() bannerImageView.af.cancelImageRequest() bannerImageView.image = nil } - + override init(frame: CGRect) { super.init(frame: frame) _init() } - + required init?(coder: NSCoder) { super.init(coder: coder) _init() } - + } extension ProfileCardView { @@ -130,8 +142,8 @@ extension ProfileCardView { authorNameLabel.isUserInteractionEnabled = false authorUsernameLabel.isUserInteractionEnabled = false bioMetaText.textView.isUserInteractionEnabled = false - statusDashboardView.isUserInteractionEnabled = false - + statusDashboardView.isUserInteractionEnabled = false + // container: V - [ bannerContainer | authorContainer | bioMetaText | infoContainer | familiarFollowersDashboardView ] container.axis = .vertical container.spacing = 8 @@ -139,13 +151,13 @@ extension ProfileCardView { container.backgroundColor = Asset.Scene.Discovery.profileCardBackground.color addSubview(container) container.pinToParent() - + // bannerContainer let bannerContainer = UIView() bannerContainer.translatesAutoresizingMaskIntoConstraints = false container.addArrangedSubview(bannerContainer) container.setCustomSpacing(6, after: bannerContainer) - + // bannerImageView bannerImageView.translatesAutoresizingMaskIntoConstraints = false bannerContainer.addSubview(bannerImageView) @@ -156,7 +168,7 @@ extension ProfileCardView { bannerImageView.bottomAnchor.constraint(equalTo: bannerContainer.bottomAnchor), bannerImageView.widthAnchor.constraint(equalTo: bannerImageView.heightAnchor, multiplier: 335.0/128.0).priority(.required - 1), ]) - + // authorContainer: H - [ avatarPlaceholder | authorInfoContainer ] let authorContainer = UIStackView() authorContainer.axis = .horizontal @@ -175,7 +187,7 @@ extension ProfileCardView { avatarPlaceholder.widthAnchor.constraint(equalToConstant: ProfileCardView.avatarSize.width).priority(.required - 1), avatarPlaceholder.heightAnchor.constraint(equalToConstant: ProfileCardView.avatarSize.height - 14).priority(.required - 1), ]) - + avatarButton.translatesAutoresizingMaskIntoConstraints = false authorContainer.addSubview(avatarButton) NSLayoutConstraint.activate([ @@ -184,7 +196,7 @@ extension ProfileCardView { avatarButton.bottomAnchor.constraint(equalTo: avatarPlaceholder.bottomAnchor), avatarButton.heightAnchor.constraint(equalToConstant: ProfileCardView.avatarSize.height).priority(.required - 1), ]) - + avatarButtonBackgroundView.backgroundColor = Asset.Scene.Discovery.profileCardBackground.color avatarButtonBackgroundView.layer.masksToBounds = true avatarButtonBackgroundView.layer.cornerCurve = .continuous @@ -197,16 +209,16 @@ extension ProfileCardView { avatarButtonBackgroundView.widthAnchor.constraint(equalToConstant: ProfileCardView.avatarSize.width + 4).priority(.required - 1), avatarButtonBackgroundView.heightAnchor.constraint(equalToConstant: ProfileCardView.avatarSize.height + 4).priority(.required - 1), ]) - + // authorInfoContainer: V - [ authorNameLabel | authorUsernameLabel ] let authorInfoContainer = UIStackView() authorInfoContainer.axis = .vertical // authorInfoContainer.spacing = 2 authorContainer.addArrangedSubview(authorInfoContainer) - + authorInfoContainer.addArrangedSubview(authorNameLabel) authorInfoContainer.addArrangedSubview(authorUsernameLabel) - + // bioMetaText bioMetaTextAdaptiveMarginContainerView.contentView = bioMetaText.textView bioMetaTextAdaptiveMarginContainerView.margin = ProfileCardView.contentMargin @@ -222,24 +234,23 @@ extension ProfileCardView { infoContainerAdaptiveMarginContainerView.margin = ProfileCardView.contentMargin container.addArrangedSubview(infoContainerAdaptiveMarginContainerView) container.setCustomSpacing(16, after: infoContainerAdaptiveMarginContainerView) - + infoContainer.addArrangedSubview(statusDashboardView) let infoContainerSpacer = UIView() infoContainer.addArrangedSubview(UIView()) infoContainerSpacer.setContentHuggingPriority(.defaultLow - 100, for: .vertical) infoContainerSpacer.setContentHuggingPriority(.defaultLow - 100, for: .horizontal) - let relationshipActionButtonShadowContainer = ShadowBackgroundContainer() - relationshipActionButtonShadowContainer.translatesAutoresizingMaskIntoConstraints = false - infoContainer.addArrangedSubview(relationshipActionButtonShadowContainer) + infoContainer.addArrangedSubview(followButtonWrapper) - relationshipActionButton.translatesAutoresizingMaskIntoConstraints = false - relationshipActionButtonShadowContainer.addSubview(relationshipActionButton) - relationshipActionButton.pinToParent() + followButtonWrapper.translatesAutoresizingMaskIntoConstraints = false + followButtonWrapper.addSubview(followButton) NSLayoutConstraint.activate([ - relationshipActionButtonShadowContainer.widthAnchor.constraint(greaterThanOrEqualToConstant: ProfileCardView.friendshipActionButtonSize.width).priority(.required - 1), - relationshipActionButtonShadowContainer.heightAnchor.constraint(equalToConstant: ProfileCardView.friendshipActionButtonSize.height).priority(.required - 1), + followButton.topAnchor.constraint(lessThanOrEqualTo: followButtonWrapper.topAnchor), + followButton.leadingAnchor.constraint(equalTo: followButtonWrapper.leadingAnchor), + followButtonWrapper.trailingAnchor.constraint(equalTo: followButton.trailingAnchor), + followButtonWrapper.bottomAnchor.constraint(greaterThanOrEqualTo: followButton.bottomAnchor), ]) - + familiarFollowersDashboardViewAdaptiveMarginContainerView.contentView = familiarFollowersDashboardView familiarFollowersDashboardViewAdaptiveMarginContainerView.margin = ProfileCardView.contentMargin container.addArrangedSubview(familiarFollowersDashboardViewAdaptiveMarginContainerView) @@ -250,19 +261,23 @@ extension ProfileCardView { NSLayoutConstraint.activate([ bottomPadding.heightAnchor.constraint(equalToConstant: 8).priority(.required - 10), ]) - - relationshipActionButton.addTarget(self, action: #selector(ProfileCardView.relationshipActionButtonDidPressed(_:)), for: .touchUpInside) - + + followButton.addTarget(self, action: #selector(ProfileCardView.relationshipActionButtonDidPressed(_:)), for: .touchUpInside) + let familiarFollowersDashboardViewTapGestureRecognizer = UITapGestureRecognizer.singleTapGestureRecognizer familiarFollowersDashboardViewTapGestureRecognizer.addTarget(self, action: #selector(ProfileCardView.familiarFollowersDashboardViewDidPressed(_:))) familiarFollowersDashboardView.addGestureRecognizer(familiarFollowersDashboardViewTapGestureRecognizer) + + statusDashboardView.postDashboardMeterView.textLabel.text = L10n.Scene.Profile.Dashboard.otherPosts + statusDashboardView.followingDashboardMeterView.textLabel.text = L10n.Scene.Profile.Dashboard.otherFollowing + statusDashboardView.followersDashboardMeterView.textLabel.text = L10n.Scene.Profile.Dashboard.otherFollowers } - + public override func layoutSubviews() { updateInfoContainerLayout() super.layoutSubviews() } - + } extension ProfileCardView { @@ -273,7 +288,7 @@ extension ProfileCardView { infoContainerAdaptiveMarginContainerView.frame.size.width = frame.width infoContainerAdaptiveMarginContainerView.contentView?.frame.size.width = frame.width - 2 * infoContainerAdaptiveMarginContainerView.margin } - + private func updateInfoContainerLayout() { let isCompactAdaptive = bounds.width < 350 infoContainer.axis = isCompactAdaptive ? .vertical : .horizontal @@ -282,12 +297,108 @@ extension ProfileCardView { extension ProfileCardView { @objc private func relationshipActionButtonDidPressed(_ sender: UIButton) { - assert(sender === relationshipActionButton) - delegate?.profileCardView(self, relationshipButtonDidPressed: relationshipActionButton) + assert(sender === followButton) + delegate?.profileCardView(self, relationshipButtonDidPressed: followButton) } - + @objc private func familiarFollowersDashboardViewDidPressed(_ sender: UITapGestureRecognizer) { assert(sender.view === familiarFollowersDashboardView) delegate?.profileCardView(self, familiarFollowersDashboardViewDidPressed: familiarFollowersDashboardView) } } + +extension ProfileCardView { + func updateButtonState(with relationship: Mastodon.Entity.Relationship?, isMe: Bool) { + let buttonState: UserView.ButtonState + + if let relationship { + if isMe { + buttonState = .none + } else if relationship.following { + buttonState = .unfollow + } else if relationship.blocking || (relationship.domainBlocking ?? false) { + buttonState = .blocked + } else if relationship.requested ?? false { + buttonState = .pending + } else { + buttonState = .follow + } + } else { + buttonState = .none + } + + setButtonState(buttonState) + + } + + func setButtonState(_ state: UserView.ButtonState) { +// currentButtonState = state + + switch state { + + case .loading: + followButtonWrapper.isHidden = false + followButton.isHidden = false + followButton.configuration?.title = nil + followButton.configuration?.showsActivityIndicator = true + followButton.configuration?.background.backgroundColor = Asset.Colors.Button.userFollowing.color + followButton.configuration?.baseForegroundColor = Asset.Colors.Brand.blurple.color + followButton.isEnabled = false + + case .follow: + followButtonWrapper.isHidden = false + followButton.isHidden = false + followButton.configuration?.title = L10n.Common.Controls.Friendship.follow + followButton.configuration?.showsActivityIndicator = false + followButton.configuration?.background.backgroundColor = Asset.Colors.Button.userFollow.color + followButton.configuration?.baseForegroundColor = .white + followButton.isEnabled = true + + case .request: + followButtonWrapper.isHidden = false + followButton.isHidden = false + followButton.configuration?.title = L10n.Common.Controls.Friendship.request + followButton.configuration?.showsActivityIndicator = false + followButton.configuration?.background.backgroundColor = Asset.Colors.Button.userFollow.color + followButton.configuration?.baseForegroundColor = .white + followButton.isEnabled = true + + case .pending: + followButtonWrapper.isHidden = false + followButton.isHidden = false + followButton.configuration?.title = L10n.Common.Controls.Friendship.pending + followButton.configuration?.baseForegroundColor = Asset.Colors.Button.userFollowingTitle.color + followButton.configuration?.showsActivityIndicator = false + followButton.configuration?.background.backgroundColor = Asset.Colors.Button.userFollowing.color + followButton.isEnabled = true + + case .unfollow: + followButtonWrapper.isHidden = false + followButton.isHidden = false + followButton.configuration?.title = L10n.Common.Controls.Friendship.following + followButton.configuration?.showsActivityIndicator = false + followButton.configuration?.background.backgroundColor = Asset.Colors.Button.userFollowing.color + followButton.configuration?.baseForegroundColor = Asset.Colors.Button.userFollowingTitle.color + followButton.isEnabled = true + + case .blocked: + followButtonWrapper.isHidden = false + followButton.isHidden = false + followButton.configuration?.title = L10n.Common.Controls.Friendship.blocked + followButton.configuration?.showsActivityIndicator = false + followButton.configuration?.background.backgroundColor = Asset.Colors.Button.userBlocked.color + followButton.configuration?.baseForegroundColor = .systemRed + followButton.isEnabled = true + + case .none: + followButtonWrapper.isHidden = true + followButton.isHidden = true + followButton.configuration?.title = nil + followButton.configuration?.showsActivityIndicator = false + followButton.configuration?.background.backgroundColor = .clear + followButton.isEnabled = false + } + + followButton.titleLabel?.font = UIFontMetrics(forTextStyle: .subheadline).scaledFont(for: .boldSystemFont(ofSize: 15)) + } +} From 6cf5134ff68adf35712a058b29eec2eeac3f79e0 Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Mon, 20 Nov 2023 13:59:25 +0100 Subject: [PATCH 09/13] Properly update button-state when following people (IOS-194) --- Mastodon/Protocol/Provider/DataSourceFacade+Follow.swift | 9 ++++++--- .../Discovery/ForYou/DiscoveryForYouViewController.swift | 8 +++++++- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/Mastodon/Protocol/Provider/DataSourceFacade+Follow.swift b/Mastodon/Protocol/Provider/DataSourceFacade+Follow.swift index 6fe0005a0..6711380b8 100644 --- a/Mastodon/Protocol/Provider/DataSourceFacade+Follow.swift +++ b/Mastodon/Protocol/Provider/DataSourceFacade+Follow.swift @@ -30,15 +30,18 @@ extension DataSourceFacade { static func responseToUserFollowAction( dependency: NeedsDependency & AuthContextProvider, user: Mastodon.Entity.Account - ) async throws { + ) async throws -> Mastodon.Entity.Relationship { let selectionFeedbackGenerator = await UISelectionFeedbackGenerator() await selectionFeedbackGenerator.selectionChanged() - _ = try await dependency.context.apiService.toggleFollow( + let response = try await dependency.context.apiService.toggleFollow( user: user, authenticationBox: dependency.authContext.mastodonAuthenticationBox - ) + ).value + dependency.context.authenticationService.fetchFollowingAndBlockedAsync() + + return response } } diff --git a/Mastodon/Scene/Discovery/ForYou/DiscoveryForYouViewController.swift b/Mastodon/Scene/Discovery/ForYou/DiscoveryForYouViewController.swift index 8d1b947be..7e9e21641 100644 --- a/Mastodon/Scene/Discovery/ForYou/DiscoveryForYouViewController.swift +++ b/Mastodon/Scene/Discovery/ForYou/DiscoveryForYouViewController.swift @@ -115,8 +115,14 @@ extension DiscoveryForYouViewController: ProfileCardTableViewCellDelegate { guard let indexPath = tableView.indexPath(for: cell) else { return } guard case let .account(account, _) = viewModel.diffableDataSource?.itemIdentifier(for: indexPath) else { return } + cell.profileCardView.setButtonState(.loading) + Task { - try await DataSourceFacade.responseToUserFollowAction(dependency: self, user: account) + let newRelationship = try await DataSourceFacade.responseToUserFollowAction(dependency: self, user: account) + + await MainActor.run { + cell.profileCardView.updateButtonState(with: newRelationship, isMe: false) + } } } From cfcf20d899eae8e3e33305b243cfd4047cebde38 Mon Sep 17 00:00:00 2001 From: Rizwan Mohamed Ibrahim Date: Mon, 20 Nov 2023 20:54:08 +0530 Subject: [PATCH 10/13] Clean up commented out code related to me tab --- .../Scene/Root/MainTab/MainTabBarController.swift | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/Mastodon/Scene/Root/MainTab/MainTabBarController.swift b/Mastodon/Scene/Root/MainTab/MainTabBarController.swift index bf14825a2..996d45a65 100644 --- a/Mastodon/Scene/Root/MainTab/MainTabBarController.swift +++ b/Mastodon/Scene/Root/MainTab/MainTabBarController.swift @@ -358,22 +358,10 @@ extension MainTabBarController { guard let tab = touchedTab(by: sender) else { return } switch tab { - // todo: reconsider the "double tap to change account" feature -> https://github.com/mastodon/mastodon-ios/issues/628 -// case .me: -// guard let authContext = authContext else { return } -// assert(Thread.isMainThread) -// -// guard let nextAccount = context.nextAccount(in: authContext) else { return } -// -// Task { @MainActor in -// let isActive = try await context.authenticationService.activeMastodonUser(domain: nextAccount.domain, userID: nextAccount.userID) -// guard isActive else { return } -// self.coordinator.setup() -// } case .search: assert(Thread.isMainThread) // double tapping search tab opens the search bar without additional taps - searchViewController?.searchBarTapPublisher.send("") + searchViewController?.searchBar.becomeFirstResponder() default: break } From 450edb05744f16cc2b803320107c6a0136fae2ad Mon Sep 17 00:00:00 2001 From: Rizwan Mohamed Ibrahim Date: Tue, 21 Nov 2023 11:51:51 +0530 Subject: [PATCH 11/13] Add double tap top focus search bar for iPad --- .../Root/ContentSplitViewController.swift | 14 +++----------- .../Scene/Root/RootSplitViewController.swift | 18 ++++++++++++++++++ .../Root/Sidebar/SidebarViewController.swift | 13 ++++++------- 3 files changed, 27 insertions(+), 18 deletions(-) diff --git a/Mastodon/Scene/Root/ContentSplitViewController.swift b/Mastodon/Scene/Root/ContentSplitViewController.swift index 877bd48d8..fdc5b5ba3 100644 --- a/Mastodon/Scene/Root/ContentSplitViewController.swift +++ b/Mastodon/Scene/Root/ContentSplitViewController.swift @@ -12,6 +12,7 @@ import MastodonCore protocol ContentSplitViewControllerDelegate: AnyObject { func contentSplitViewController(_ contentSplitViewController: ContentSplitViewController, sidebarViewController: SidebarViewController, didSelectTab tab: MainTabBarController.Tab) + func contentSplitViewController(_ contentSplitViewController: ContentSplitViewController, sidebarViewController: SidebarViewController, didDoubleTapTab tab: MainTabBarController.Tab) } final class ContentSplitViewController: UIViewController, NeedsDependency { @@ -121,16 +122,7 @@ extension ContentSplitViewController: SidebarViewControllerDelegate { } func sidebarViewController(_ sidebarViewController: SidebarViewController, didDoubleTapItem item: SidebarViewModel.Item, sourceView: UIView) { - guard case let .tab(tab) = item, tab == .me else { return } - guard let authContext = authContext else { return } - assert(Thread.isMainThread) - - guard let nextAccount = context.nextAccount(in: authContext) else { return } - - Task { @MainActor in - let isActive = try await context.authenticationService.activeMastodonUser(domain: nextAccount.domain, userID: nextAccount.userID) - guard isActive else { return } - self.coordinator.setup() - } + guard case let .tab(tab) = item else { return } + delegate?.contentSplitViewController(self, sidebarViewController: sidebarViewController, didDoubleTapTab: tab) } } diff --git a/Mastodon/Scene/Root/RootSplitViewController.swift b/Mastodon/Scene/Root/RootSplitViewController.swift index 10ed4c441..ea87f2500 100644 --- a/Mastodon/Scene/Root/RootSplitViewController.swift +++ b/Mastodon/Scene/Root/RootSplitViewController.swift @@ -157,6 +157,24 @@ extension RootSplitViewController: ContentSplitViewControllerDelegate { } } + + func contentSplitViewController(_ contentSplitViewController: ContentSplitViewController, sidebarViewController: SidebarViewController, didDoubleTapTab tab: MainTabBarController.Tab) { + guard let _ = MainTabBarController.Tab.allCases.firstIndex(of: tab) else { + assertionFailure() + return + } + + switch tab { + case .search: + // allow double tap to focus search bar only when is not primary display (iPad potrait) + guard !isPrimaryDisplay else { + return + } + contentSplitViewController.mainTabBarController.searchViewController?.searchBar.becomeFirstResponder() + default: + break + } + } } // MARK: - UISplitViewControllerDelegate diff --git a/Mastodon/Scene/Root/Sidebar/SidebarViewController.swift b/Mastodon/Scene/Root/Sidebar/SidebarViewController.swift index ac6342f83..b9a7d80f1 100644 --- a/Mastodon/Scene/Root/Sidebar/SidebarViewController.swift +++ b/Mastodon/Scene/Root/Sidebar/SidebarViewController.swift @@ -128,13 +128,12 @@ extension SidebarViewController { sidebarLongPressGestureRecognizer.addTarget(self, action: #selector(SidebarViewController.sidebarLongPressGestureRecognizerHandler(_:))) collectionView.addGestureRecognizer(sidebarLongPressGestureRecognizer) - // todo: reconsider the "double tap to change account" feature -> https://github.com/mastodon/mastodon-ios/issues/628 -// let sidebarDoubleTapGestureRecognizer = UITapGestureRecognizer() -// sidebarDoubleTapGestureRecognizer.numberOfTapsRequired = 2 -// sidebarDoubleTapGestureRecognizer.addTarget(self, action: #selector(SidebarViewController.sidebarDoubleTapGestureRecognizerHandler(_:))) -// sidebarDoubleTapGestureRecognizer.delaysTouchesEnded = false -// sidebarDoubleTapGestureRecognizer.cancelsTouchesInView = true -// collectionView.addGestureRecognizer(sidebarDoubleTapGestureRecognizer) + let sidebarDoubleTapGestureRecognizer = UITapGestureRecognizer() + sidebarDoubleTapGestureRecognizer.numberOfTapsRequired = 2 + sidebarDoubleTapGestureRecognizer.addTarget(self, action: #selector(SidebarViewController.sidebarDoubleTapGestureRecognizerHandler(_:))) + sidebarDoubleTapGestureRecognizer.delaysTouchesEnded = false + sidebarDoubleTapGestureRecognizer.cancelsTouchesInView = true + collectionView.addGestureRecognizer(sidebarDoubleTapGestureRecognizer) } From 5b55be092aa4a65d8527b9b4a8b5b8efa11eda76 Mon Sep 17 00:00:00 2001 From: Rizwan Mohamed Ibrahim Date: Tue, 21 Nov 2023 11:53:13 +0530 Subject: [PATCH 12/13] Fix crash on iPad non primary display focusing search bar Crash happens when we try to focus search bar when in iPad portrait because of custom hight search bar don't have correct intrinsic size --- .../Scene/Search/SearchDetail/SearchDetailViewController.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Mastodon/Scene/Search/SearchDetail/SearchDetailViewController.swift b/Mastodon/Scene/Search/SearchDetail/SearchDetailViewController.swift index 9b1491ed7..d88c93b71 100644 --- a/Mastodon/Scene/Search/SearchDetail/SearchDetailViewController.swift +++ b/Mastodon/Scene/Search/SearchDetail/SearchDetailViewController.swift @@ -233,6 +233,7 @@ extension SearchDetailViewController { navigationItem.setHidesBackButton(true, animated: false) navigationItem.titleView = nil navigationItem.searchController = searchController + navigationItem.preferredSearchBarPlacement = .stacked searchController.searchBar.sizeToFit() } From f8251981826ec4d7e9243ab7664aa7190028470f Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Tue, 21 Nov 2023 12:13:25 +0100 Subject: [PATCH 13/13] Check for me (IOS-194) --- .../Discovery/ForYou/DiscoveryForYouViewController.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Mastodon/Scene/Discovery/ForYou/DiscoveryForYouViewController.swift b/Mastodon/Scene/Discovery/ForYou/DiscoveryForYouViewController.swift index 7e9e21641..4d43e319a 100644 --- a/Mastodon/Scene/Discovery/ForYou/DiscoveryForYouViewController.swift +++ b/Mastodon/Scene/Discovery/ForYou/DiscoveryForYouViewController.swift @@ -120,8 +120,10 @@ extension DiscoveryForYouViewController: ProfileCardTableViewCellDelegate { Task { let newRelationship = try await DataSourceFacade.responseToUserFollowAction(dependency: self, user: account) + let isMe = (account.id == authContext.mastodonAuthenticationBox.userID) + await MainActor.run { - cell.profileCardView.updateButtonState(with: newRelationship, isMe: false) + cell.profileCardView.updateButtonState(with: newRelationship, isMe: isMe) } } }