diff --git a/Localization/StringsConvertor/input/Base.lproj/app.json b/Localization/StringsConvertor/input/Base.lproj/app.json index 96b2150ce..ea046bfbc 100644 --- a/Localization/StringsConvertor/input/Base.lproj/app.json +++ b/Localization/StringsConvertor/input/Base.lproj/app.json @@ -96,7 +96,7 @@ "tabs": { "home": "Home", "search": "Search", - "notification": "Notification", + "notifications": "Notifications", "profile": "Profile" }, "keyboard": { diff --git a/Localization/app.json b/Localization/app.json index 96b2150ce..ea046bfbc 100644 --- a/Localization/app.json +++ b/Localization/app.json @@ -96,7 +96,7 @@ "tabs": { "home": "Home", "search": "Search", - "notification": "Notification", + "notifications": "Notifications", "profile": "Profile" }, "keyboard": { diff --git a/Mastodon/Coordinator/SceneCoordinator.swift b/Mastodon/Coordinator/SceneCoordinator.swift index 9e6307a38..3d774ba0e 100644 --- a/Mastodon/Coordinator/SceneCoordinator.swift +++ b/Mastodon/Coordinator/SceneCoordinator.swift @@ -71,8 +71,8 @@ final public class SceneCoordinator { self.setup() try await Task.sleep(nanoseconds: .second * 1) - // redirect to notification tab - self.switchToTabBar(tab: .notification) + // redirect to notifications tab + self.switchToTabBar(tab: .notifications) // Delay in next run loop DispatchQueue.main.async { [weak self] in diff --git a/Mastodon/Scene/Root/MainTab/MainTabBarController.swift b/Mastodon/Scene/Root/MainTab/MainTabBarController.swift index 13db77355..2e5d5ae58 100644 --- a/Mastodon/Scene/Root/MainTab/MainTabBarController.swift +++ b/Mastodon/Scene/Root/MainTab/MainTabBarController.swift @@ -51,7 +51,7 @@ class MainTabBarController: UITabBarController { case home case search case compose - case notification + case notifications case me var tag: Int { @@ -63,7 +63,7 @@ class MainTabBarController: UITabBarController { case .home: return L10n.Common.Controls.Tabs.home case .search: return L10n.Common.Controls.Tabs.search case .compose: return L10n.Common.Controls.Actions.compose - case .notification: return L10n.Common.Controls.Tabs.notification + case .notifications: return L10n.Common.Controls.Tabs.notifications case .me: return L10n.Common.Controls.Tabs.profile } } @@ -73,7 +73,7 @@ class MainTabBarController: UITabBarController { case .home: return Asset.ObjectsAndTools.house.image.withRenderingMode(.alwaysTemplate) case .search: return Asset.ObjectsAndTools.magnifyingglass.image.withRenderingMode(.alwaysTemplate) case .compose: return Asset.ObjectsAndTools.squareAndPencil.image.withRenderingMode(.alwaysTemplate) - case .notification: return Asset.ObjectsAndTools.bell.image.withRenderingMode(.alwaysTemplate) + case .notifications: return Asset.ObjectsAndTools.bell.image.withRenderingMode(.alwaysTemplate) case .me: return UIImage(systemName: "person")! } } @@ -83,7 +83,7 @@ class MainTabBarController: UITabBarController { case .home: return Asset.ObjectsAndTools.houseFill.image.withRenderingMode(.alwaysTemplate) case .search: return Asset.ObjectsAndTools.magnifyingglassFill.image.withRenderingMode(.alwaysTemplate) case .compose: return Asset.ObjectsAndTools.squareAndPencil.image.withRenderingMode(.alwaysTemplate) - case .notification: return Asset.ObjectsAndTools.bellFill.image.withRenderingMode(.alwaysTemplate) + case .notifications: return Asset.ObjectsAndTools.bellFill.image.withRenderingMode(.alwaysTemplate) case .me: return UIImage(systemName: "person.fill")! } } @@ -93,7 +93,7 @@ class MainTabBarController: UITabBarController { case .home: return Asset.ObjectsAndTools.house.image.withRenderingMode(.alwaysTemplate).resized(size: CGSize(width: 80, height: 80)) case .search: return Asset.ObjectsAndTools.magnifyingglass.image.withRenderingMode(.alwaysTemplate).resized(size: CGSize(width: 80, height: 80)) case .compose: return Asset.ObjectsAndTools.squareAndPencil.image.withRenderingMode(.alwaysTemplate).resized(size: CGSize(width: 80, height: 80)) - case .notification: return Asset.ObjectsAndTools.bell.image.withRenderingMode(.alwaysTemplate).resized(size: CGSize(width: 80, height: 80)) + case .notifications: return Asset.ObjectsAndTools.bell.image.withRenderingMode(.alwaysTemplate).resized(size: CGSize(width: 80, height: 80)) case .me: return UIImage(systemName: "person", withConfiguration: UIImage.SymbolConfiguration(pointSize: 80))! } } @@ -103,7 +103,7 @@ class MainTabBarController: UITabBarController { case .home: return Asset.ObjectsAndTools.house.image.withRenderingMode(.alwaysTemplate) case .search: return Asset.ObjectsAndTools.magnifyingglass.image.withRenderingMode(.alwaysTemplate) case .compose: return Asset.ObjectsAndTools.squareAndPencil.image.withRenderingMode(.alwaysTemplate) - case .notification: return Asset.ObjectsAndTools.bell.image.withRenderingMode(.alwaysTemplate) + case .notifications: return Asset.ObjectsAndTools.bell.image.withRenderingMode(.alwaysTemplate) case .me: return UIImage(systemName: "person")! } } @@ -129,7 +129,7 @@ class MainTabBarController: UITabBarController { viewController = _viewController case .compose: viewController = UIViewController() - case .notification: + case .notifications: let _viewController = NotificationViewController() _viewController.context = context _viewController.coordinator = coordinator @@ -274,7 +274,7 @@ extension MainTabBarController { } ?? false let image: UIImage = { - if currentTab == .notification { + if currentTab == .notifications { return hasUnreadPushNotification ? Asset.ObjectsAndTools.bellBadgeFill.image.withRenderingMode(.alwaysTemplate) : Asset.ObjectsAndTools.bellFill.image.withRenderingMode(.alwaysTemplate) } else { return hasUnreadPushNotification ? Asset.ObjectsAndTools.bellBadge.image.withRenderingMode(.alwaysTemplate) : Asset.ObjectsAndTools.bell.image.withRenderingMode(.alwaysTemplate) @@ -654,7 +654,7 @@ extension MainTabBarController { let tabs: [Tab] = [ .home, .search, - .notification, + .notifications, .me ] for (i, tab) in tabs.enumerated() { diff --git a/Mastodon/Scene/Root/Sidebar/SidebarViewModel.swift b/Mastodon/Scene/Root/Sidebar/SidebarViewModel.swift index 2e1a0f361..f6e90dcd9 100644 --- a/Mastodon/Scene/Root/Sidebar/SidebarViewModel.swift +++ b/Mastodon/Scene/Root/Sidebar/SidebarViewModel.swift @@ -100,7 +100,7 @@ extension SidebarViewModel { .store(in: &cell.disposeBag) switch item { - case .notification: + case .notifications: Publishers.CombineLatest( self.context.notificationService.unreadNotificationCountDidUpdate, self.$currentTab @@ -116,7 +116,7 @@ extension SidebarViewModel { }() let image: UIImage = { - if currentTab == .notification { + if currentTab == .notifications { return hasUnreadPushNotification ? Asset.ObjectsAndTools.bellBadgeFill.image.withRenderingMode(.alwaysTemplate) : Asset.ObjectsAndTools.bellFill.image.withRenderingMode(.alwaysTemplate) } else { return hasUnreadPushNotification ? Asset.ObjectsAndTools.bellBadge.image.withRenderingMode(.alwaysTemplate) : Asset.ObjectsAndTools.bell.image.withRenderingMode(.alwaysTemplate) @@ -193,7 +193,7 @@ extension SidebarViewModel { let items: [Item] = [ .tab(.home), .tab(.search), - .tab(.notification), + .tab(.notifications), .tab(.me), .setting, ] diff --git a/Mastodon/Scene/Share/View/Content/NotificationView+Configuration.swift b/Mastodon/Scene/Share/View/Content/NotificationView+Configuration.swift index c7dbdb33f..018628473 100644 --- a/Mastodon/Scene/Share/View/Content/NotificationView+Configuration.swift +++ b/Mastodon/Scene/Share/View/Content/NotificationView+Configuration.swift @@ -111,6 +111,7 @@ extension NotificationView { self.viewModel.notificationIndicatorText = nil return } + self.viewModel.type = type func createMetaContent(text: String, emojis: MastodonContent.Emojis) -> MetaContent { let content = MastodonContent(content: text, emojis: emojis) diff --git a/Mastodon/Scene/Share/View/TableviewCell/StatusThreadRootTableViewCell.swift b/Mastodon/Scene/Share/View/TableviewCell/StatusThreadRootTableViewCell.swift index 64f3456b5..350bf8660 100644 --- a/Mastodon/Scene/Share/View/TableviewCell/StatusThreadRootTableViewCell.swift +++ b/Mastodon/Scene/Share/View/TableviewCell/StatusThreadRootTableViewCell.swift @@ -96,7 +96,6 @@ extension StatusThreadRootTableViewCell { override var accessibilityElements: [Any]? { get { var elements = [ - statusView.headerContainerView, statusView.authorView, statusView.viewModel.isContentReveal ? statusView.contentMetaText.textView diff --git a/Mastodon/Supporting Files/SceneDelegate.swift b/Mastodon/Supporting Files/SceneDelegate.swift index bf02b8031..4476477fd 100644 --- a/Mastodon/Supporting Files/SceneDelegate.swift +++ b/Mastodon/Supporting Files/SceneDelegate.swift @@ -175,7 +175,7 @@ extension SceneDelegate { return false } - coordinator.switchToTabBar(tab: .notification) + coordinator.switchToTabBar(tab: .notifications) case "org.joinmastodon.app.new-post": if coordinator?.tabBarController.topMost is ComposeViewController { diff --git a/MastodonSDK/Package.resolved b/MastodonSDK/Package.resolved index 06843faa3..4a4fa51de 100644 --- a/MastodonSDK/Package.resolved +++ b/MastodonSDK/Package.resolved @@ -1,241 +1,257 @@ { - "object": { - "pins": [ - { - "package": "Alamofire", - "repositoryURL": "https://github.com/Alamofire/Alamofire.git", - "state": { - "branch": null, - "revision": "8dd85aee02e39dd280c75eef88ffdb86eed4b07b", - "version": "5.6.2" - } - }, - { - "package": "AlamofireImage", - "repositoryURL": "https://github.com/Alamofire/AlamofireImage.git", - "state": { - "branch": null, - "revision": "98cbb00ce0ec5fc8e52a5b50a6bfc08d3e5aee10", - "version": "4.2.0" - } - }, - { - "package": "CommonOSLog", - "repositoryURL": "https://github.com/MainasuK/CommonOSLog", - "state": { - "branch": null, - "revision": "c121624a30698e9886efe38aebb36ff51c01b6c2", - "version": "0.1.1" - } - }, - { - "package": "FaviconFinder", - "repositoryURL": "https://github.com/will-lumley/FaviconFinder.git", - "state": { - "branch": null, - "revision": "1f74844f77f79b95c0bb0130b3a87d4f340e6d3a", - "version": "3.3.0" - } - }, - { - "package": "FLAnimatedImage", - "repositoryURL": "https://github.com/Flipboard/FLAnimatedImage.git", - "state": { - "branch": null, - "revision": "d4f07b6f164d53c1212c3e54d6460738b1981e9f", - "version": "1.0.17" - } - }, - { - "package": "FPSIndicator", - "repositoryURL": "https://github.com/MainasuK/FPSIndicator.git", - "state": { - "branch": null, - "revision": "e4a5067ccd5293b024c767f09e51056afd4a4796", - "version": "1.1.0" - } - }, - { - "package": "Fuzi", - "repositoryURL": "https://github.com/cezheng/Fuzi.git", - "state": { - "branch": null, - "revision": "f08c8323da21e985f3772610753bcfc652c2103f", - "version": "3.1.3" - } - }, - { - "package": "KeychainAccess", - "repositoryURL": "https://github.com/kishikawakatsumi/KeychainAccess.git", - "state": { - "branch": null, - "revision": "84e546727d66f1adc5439debad16270d0fdd04e7", - "version": "4.2.2" - } - }, - { - "package": "MetaTextKit", - "repositoryURL": "https://github.com/TwidereProject/MetaTextKit.git", - "state": { - "branch": null, - "revision": "dcd5255d6930c2fab408dc8562c577547e477624", - "version": "2.2.5" - } - }, - { - "package": "Nuke", - "repositoryURL": "https://github.com/kean/Nuke.git", - "state": { - "branch": null, - "revision": "a002b7fd786f2df2ed4333fe73a9727499fd9d97", - "version": "10.11.2" - } - }, - { - "package": "NukeFLAnimatedImagePlugin", - "repositoryURL": "https://github.com/kean/Nuke-FLAnimatedImage-Plugin.git", - "state": { - "branch": null, - "revision": "b59c346a7d536336db3b0f12c72c6e53ee709e16", - "version": "8.0.0" - } - }, - { - "package": "Pageboy", - "repositoryURL": "https://github.com/uias/Pageboy", - "state": { - "branch": null, - "revision": "af8fa81788b893205e1ff42ddd88c5b0b315d7c5", - "version": "3.7.0" - } - }, - { - "package": "PanModal", - "repositoryURL": "https://github.com/slackhq/PanModal.git", - "state": { - "branch": null, - "revision": "b012aecb6b67a8e46369227f893c12544846613f", - "version": "1.2.7" - } - }, - { - "package": "SDWebImage", - "repositoryURL": "https://github.com/SDWebImage/SDWebImage.git", - "state": { - "branch": null, - "revision": "9248fe561a2a153916fb9597e3af4434784c6d32", - "version": "5.13.4" - } - }, - { - "package": "swift-collections", - "repositoryURL": "https://github.com/apple/swift-collections.git", - "state": { - "branch": null, - "revision": "f504716c27d2e5d4144fa4794b12129301d17729", - "version": "1.0.3" - } - }, - { - "package": "swift-nio", - "repositoryURL": "https://github.com/apple/swift-nio.git", - "state": { - "branch": null, - "revision": "546610d52b19be3e19935e0880bb06b9c03f5cef", - "version": "1.14.4" - } - }, - { - "package": "swift-nio-zlib-support", - "repositoryURL": "https://github.com/apple/swift-nio-zlib-support.git", - "state": { - "branch": null, - "revision": "37760e9a52030bb9011972c5213c3350fa9d41fd", - "version": "1.0.0" - } - }, - { - "package": "SwiftSoup", - "repositoryURL": "https://github.com/scinfu/SwiftSoup.git", - "state": { - "branch": null, - "revision": "6778575285177365cbad3e5b8a72f2a20583cfec", - "version": "2.4.3" - } - }, - { - "package": "Introspect", - "repositoryURL": "https://github.com/siteline/SwiftUI-Introspect.git", - "state": { - "branch": null, - "revision": "f2616860a41f9d9932da412a8978fec79c06fe24", - "version": "0.1.4" - } - }, - { - "package": "SwiftyJSON", - "repositoryURL": "https://github.com/SwiftyJSON/SwiftyJSON.git", - "state": { - "branch": null, - "revision": "b3dcd7dbd0d488e1a7077cb33b00f2083e382f07", - "version": "5.0.1" - } - }, - { - "package": "TabBarPager", - "repositoryURL": "https://github.com/TwidereProject/TabBarPager.git", - "state": { - "branch": null, - "revision": "488aa66d157a648901b61721212c0dec23d27ee5", - "version": "0.1.0" - } - }, - { - "package": "Tabman", - "repositoryURL": "https://github.com/uias/Tabman", - "state": { - "branch": null, - "revision": "4a4f7c755b875ffd4f9ef10d67a67883669d2465", - "version": "2.13.0" - } - }, - { - "package": "ThirdPartyMailer", - "repositoryURL": "https://github.com/vtourraine/ThirdPartyMailer.git", - "state": { - "branch": null, - "revision": "44c1cfaa6969963f22691aa67f88a69e3b6d651f", - "version": "2.1.0" - } - }, - { - "package": "TOCropViewController", - "repositoryURL": "https://github.com/TimOliver/TOCropViewController.git", - "state": { - "branch": null, - "revision": "d0470491f56e734731bbf77991944c0dfdee3e0e", - "version": "2.6.1" - } - }, - { - "package": "UIHostingConfigurationBackport", - "repositoryURL": "https://github.com/woxtu/UIHostingConfigurationBackport.git", - "state": { - "branch": null, - "revision": "6091f2d38faa4b24fc2ca0389c651e2f666624a3", - "version": "0.1.0" - } - }, - { - "package": "UITextView+Placeholder", - "repositoryURL": "https://github.com/MainasuK/UITextView-Placeholder.git", - "state": { - "branch": null, - "revision": "20f513ded04a040cdf5467f0891849b1763ede3b", - "version": "1.4.1" - } + "pins" : [ + { + "identity" : "alamofire", + "kind" : "remoteSourceControl", + "location" : "https://github.com/Alamofire/Alamofire.git", + "state" : { + "revision" : "8dd85aee02e39dd280c75eef88ffdb86eed4b07b", + "version" : "5.6.2" } - ] - }, - "version": 1 + }, + { + "identity" : "alamofireimage", + "kind" : "remoteSourceControl", + "location" : "https://github.com/Alamofire/AlamofireImage.git", + "state" : { + "revision" : "98cbb00ce0ec5fc8e52a5b50a6bfc08d3e5aee10", + "version" : "4.2.0" + } + }, + { + "identity" : "commonoslog", + "kind" : "remoteSourceControl", + "location" : "https://github.com/MainasuK/CommonOSLog", + "state" : { + "revision" : "c121624a30698e9886efe38aebb36ff51c01b6c2", + "version" : "0.1.1" + } + }, + { + "identity" : "faviconfinder", + "kind" : "remoteSourceControl", + "location" : "https://github.com/will-lumley/FaviconFinder.git", + "state" : { + "revision" : "1f74844f77f79b95c0bb0130b3a87d4f340e6d3a", + "version" : "3.3.0" + } + }, + { + "identity" : "flanimatedimage", + "kind" : "remoteSourceControl", + "location" : "https://github.com/Flipboard/FLAnimatedImage.git", + "state" : { + "revision" : "d4f07b6f164d53c1212c3e54d6460738b1981e9f", + "version" : "1.0.17" + } + }, + { + "identity" : "fpsindicator", + "kind" : "remoteSourceControl", + "location" : "https://github.com/MainasuK/FPSIndicator.git", + "state" : { + "revision" : "e4a5067ccd5293b024c767f09e51056afd4a4796", + "version" : "1.1.0" + } + }, + { + "identity" : "fuzi", + "kind" : "remoteSourceControl", + "location" : "https://github.com/cezheng/Fuzi.git", + "state" : { + "revision" : "f08c8323da21e985f3772610753bcfc652c2103f", + "version" : "3.1.3" + } + }, + { + "identity" : "keychainaccess", + "kind" : "remoteSourceControl", + "location" : "https://github.com/kishikawakatsumi/KeychainAccess.git", + "state" : { + "revision" : "84e546727d66f1adc5439debad16270d0fdd04e7", + "version" : "4.2.2" + } + }, + { + "identity" : "kingfisher", + "kind" : "remoteSourceControl", + "location" : "https://github.com/onevcat/Kingfisher.git", + "state" : { + "revision" : "44e891bdb61426a95e31492a67c7c0dfad1f87c5", + "version" : "7.4.1" + } + }, + { + "identity" : "metatextkit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/TwidereProject/MetaTextKit.git", + "state" : { + "revision" : "dcd5255d6930c2fab408dc8562c577547e477624", + "version" : "2.2.5" + } + }, + { + "identity" : "nextlevelsessionexporter", + "kind" : "remoteSourceControl", + "location" : "https://github.com/NextLevel/NextLevelSessionExporter.git", + "state" : { + "revision" : "b6c0cce1aa37fe1547d694f958fac3c3524b74da", + "version" : "0.4.6" + } + }, + { + "identity" : "nuke", + "kind" : "remoteSourceControl", + "location" : "https://github.com/kean/Nuke.git", + "state" : { + "revision" : "a002b7fd786f2df2ed4333fe73a9727499fd9d97", + "version" : "10.11.2" + } + }, + { + "identity" : "nuke-flanimatedimage-plugin", + "kind" : "remoteSourceControl", + "location" : "https://github.com/kean/Nuke-FLAnimatedImage-Plugin.git", + "state" : { + "revision" : "b59c346a7d536336db3b0f12c72c6e53ee709e16", + "version" : "8.0.0" + } + }, + { + "identity" : "pageboy", + "kind" : "remoteSourceControl", + "location" : "https://github.com/uias/Pageboy", + "state" : { + "revision" : "af8fa81788b893205e1ff42ddd88c5b0b315d7c5", + "version" : "3.7.0" + } + }, + { + "identity" : "panmodal", + "kind" : "remoteSourceControl", + "location" : "https://github.com/slackhq/PanModal.git", + "state" : { + "revision" : "b012aecb6b67a8e46369227f893c12544846613f", + "version" : "1.2.7" + } + }, + { + "identity" : "sdwebimage", + "kind" : "remoteSourceControl", + "location" : "https://github.com/SDWebImage/SDWebImage.git", + "state" : { + "revision" : "9248fe561a2a153916fb9597e3af4434784c6d32", + "version" : "5.13.4" + } + }, + { + "identity" : "stripes", + "kind" : "remoteSourceControl", + "location" : "https://github.com/eneko/Stripes.git", + "state" : { + "revision" : "d533fd44b8043a3abbf523e733599173d6f98c11", + "version" : "0.2.0" + } + }, + { + "identity" : "swift-collections", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-collections.git", + "state" : { + "revision" : "f504716c27d2e5d4144fa4794b12129301d17729", + "version" : "1.0.3" + } + }, + { + "identity" : "swift-nio", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-nio.git", + "state" : { + "revision" : "546610d52b19be3e19935e0880bb06b9c03f5cef", + "version" : "1.14.4" + } + }, + { + "identity" : "swift-nio-zlib-support", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-nio-zlib-support.git", + "state" : { + "revision" : "37760e9a52030bb9011972c5213c3350fa9d41fd", + "version" : "1.0.0" + } + }, + { + "identity" : "swiftsoup", + "kind" : "remoteSourceControl", + "location" : "https://github.com/scinfu/SwiftSoup.git", + "state" : { + "revision" : "6778575285177365cbad3e5b8a72f2a20583cfec", + "version" : "2.4.3" + } + }, + { + "identity" : "swiftui-introspect", + "kind" : "remoteSourceControl", + "location" : "https://github.com/siteline/SwiftUI-Introspect.git", + "state" : { + "revision" : "f2616860a41f9d9932da412a8978fec79c06fe24", + "version" : "0.1.4" + } + }, + { + "identity" : "tabbarpager", + "kind" : "remoteSourceControl", + "location" : "https://github.com/TwidereProject/TabBarPager.git", + "state" : { + "revision" : "488aa66d157a648901b61721212c0dec23d27ee5", + "version" : "0.1.0" + } + }, + { + "identity" : "tabman", + "kind" : "remoteSourceControl", + "location" : "https://github.com/uias/Tabman", + "state" : { + "revision" : "4a4f7c755b875ffd4f9ef10d67a67883669d2465", + "version" : "2.13.0" + } + }, + { + "identity" : "thirdpartymailer", + "kind" : "remoteSourceControl", + "location" : "https://github.com/vtourraine/ThirdPartyMailer.git", + "state" : { + "revision" : "44c1cfaa6969963f22691aa67f88a69e3b6d651f", + "version" : "2.1.0" + } + }, + { + "identity" : "tocropviewcontroller", + "kind" : "remoteSourceControl", + "location" : "https://github.com/TimOliver/TOCropViewController.git", + "state" : { + "revision" : "d0470491f56e734731bbf77991944c0dfdee3e0e", + "version" : "2.6.1" + } + }, + { + "identity" : "uihostingconfigurationbackport", + "kind" : "remoteSourceControl", + "location" : "https://github.com/woxtu/UIHostingConfigurationBackport.git", + "state" : { + "revision" : "6091f2d38faa4b24fc2ca0389c651e2f666624a3", + "version" : "0.1.0" + } + }, + { + "identity" : "uitextview-placeholder", + "kind" : "remoteSourceControl", + "location" : "https://github.com/MainasuK/UITextView-Placeholder.git", + "state" : { + "revision" : "20f513ded04a040cdf5467f0891849b1763ede3b", + "version" : "1.4.1" + } + } + ], + "version" : 2 } diff --git a/MastodonSDK/Sources/CoreDataStack/Entity/Transient/MastodonNotificationType.swift b/MastodonSDK/Sources/CoreDataStack/Entity/Transient/MastodonNotificationType.swift index 9e3029f26..455230d5e 100644 --- a/MastodonSDK/Sources/CoreDataStack/Entity/Transient/MastodonNotificationType.swift +++ b/MastodonSDK/Sources/CoreDataStack/Entity/Transient/MastodonNotificationType.swift @@ -7,7 +7,7 @@ import Foundation -public enum MastodonNotificationType: RawRepresentable { +public enum MastodonNotificationType: RawRepresentable, Equatable { case follow case followRequest case mention diff --git a/MastodonSDK/Sources/MastodonLocalization/Generated/Strings.swift b/MastodonSDK/Sources/MastodonLocalization/Generated/Strings.swift index ee56f9e31..0392d2b05 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Generated/Strings.swift +++ b/MastodonSDK/Sources/MastodonLocalization/Generated/Strings.swift @@ -366,8 +366,8 @@ public enum L10n { public enum Tabs { /// Home public static let home = L10n.tr("Localizable", "Common.Controls.Tabs.Home", fallback: "Home") - /// Notification - public static let notification = L10n.tr("Localizable", "Common.Controls.Tabs.Notification", fallback: "Notification") + /// Notifications + public static let notifications = L10n.tr("Localizable", "Common.Controls.Tabs.Notifications", fallback: "Notifications") /// Profile public static let profile = L10n.tr("Localizable", "Common.Controls.Tabs.Profile", fallback: "Profile") /// Search diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/Base.lproj/Localizable.strings b/MastodonSDK/Sources/MastodonLocalization/Resources/Base.lproj/Localizable.strings index 2a3f1efbf..adeaad07b 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/Base.lproj/Localizable.strings +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/Base.lproj/Localizable.strings @@ -131,7 +131,7 @@ Please check your internet connection."; "Common.Controls.Status.Visibility.PrivateFromMe" = "Only my followers can see this post."; "Common.Controls.Status.Visibility.Unlisted" = "Everyone can see this post but not display in the public timeline."; "Common.Controls.Tabs.Home" = "Home"; -"Common.Controls.Tabs.Notification" = "Notification"; +"Common.Controls.Tabs.Notifications" = "Notifications"; "Common.Controls.Tabs.Profile" = "Profile"; "Common.Controls.Tabs.Search" = "Search"; "Common.Controls.Timeline.Filtered" = "Filtered"; diff --git a/MastodonSDK/Sources/MastodonUI/View/Content/NotificationView+ViewModel.swift b/MastodonSDK/Sources/MastodonUI/View/Content/NotificationView+ViewModel.swift index 7c751d756..714cf676d 100644 --- a/MastodonSDK/Sources/MastodonUI/View/Content/NotificationView+ViewModel.swift +++ b/MastodonSDK/Sources/MastodonUI/View/Content/NotificationView+ViewModel.swift @@ -25,7 +25,8 @@ extension NotificationView { let logger = Logger(subsystem: "NotificationView", category: "ViewModel") @Published public var authContext: AuthContext? - + + @Published public var type: MastodonNotificationType? @Published public var notificationIndicatorText: MetaContent? @Published public var authorAvatarImage: UIImage? @@ -54,7 +55,7 @@ extension NotificationView.ViewModel { bindAuthor(notificationView: notificationView) bindAuthorMenu(notificationView: notificationView) bindFollowRequest(notificationView: notificationView) - + $authContext .assign(to: \.authContext, on: notificationView.statusView.viewModel) .store(in: &disposeBag) @@ -100,20 +101,21 @@ extension NotificationView.ViewModel { } .store(in: &disposeBag) // timestamp - Publishers.CombineLatest( + let formattedTimestamp = Publishers.CombineLatest( $timestamp, timestampUpdatePublisher.prepend(Date()).eraseToAnyPublisher() ) - .sink { [weak self] timestamp, _ in - guard let timestamp = timestamp else { - notificationView.dateLabel.configure(content: PlaintextMetaContent(string: "")) - return - } - - let text = timestamp.localizedTimeAgoSinceNow - notificationView.dateLabel.configure(content: PlaintextMetaContent(string: text)) + .map { timestamp, _ in + timestamp?.localizedTimeAgoSinceNow ?? "" } - .store(in: &disposeBag) + .removeDuplicates() + + formattedTimestamp + .sink { timestamp in + notificationView.dateLabel.configure(content: PlaintextMetaContent(string: timestamp)) + } + .store(in: &disposeBag) + // notification type indicator $notificationIndicatorText .sink { text in @@ -124,6 +126,76 @@ extension NotificationView.ViewModel { } } .store(in: &disposeBag) + + Publishers.CombineLatest4( + $authorName, + $authorUsername, + $notificationIndicatorText, + formattedTimestamp + ) + .sink { name, username, type, timestamp in + notificationView.accessibilityLabel = [ + "\(name?.string ?? "") \(type?.string ?? "")", + username.map { "@\($0)" } ?? "", + timestamp + ].joined(separator: ", ") + if !notificationView.statusView.isHidden { + notificationView.accessibilityLabel! += ", " + (notificationView.statusView.accessibilityLabel ?? "") + } + if !notificationView.quoteStatusViewContainerView.isHidden { + notificationView.accessibilityLabel! += ", " + (notificationView.quoteStatusView.accessibilityLabel ?? "") + } + } + .store(in: &disposeBag) + + Publishers.CombineLatest( + $authorAvatarImage, + $type + ) + .sink { avatarImage, type in + var actions = [UIAccessibilityCustomAction]() + + // these notifications can be directly actioned to view the profile + if type != .follow, type != .followRequest { + actions.append( + UIAccessibilityCustomAction( + name: L10n.Common.Controls.Status.showUserProfile, + image: avatarImage + ) { [weak notificationView] _ in + guard let notificationView = notificationView, let delegate = notificationView.delegate else { return false } + delegate.notificationView(notificationView, authorAvatarButtonDidPressed: notificationView.avatarButton) + return true + } + ) + } + + if type == .followRequest { + actions.append( + UIAccessibilityCustomAction( + name: L10n.Common.Controls.Actions.confirm, + image: Asset.Editing.checkmark20.image + ) { [weak notificationView] _ in + guard let notificationView = notificationView, let delegate = notificationView.delegate else { return false } + delegate.notificationView(notificationView, acceptFollowRequestButtonDidPressed: notificationView.acceptFollowRequestButton) + return true + } + ) + + actions.append( + UIAccessibilityCustomAction( + name: L10n.Common.Controls.Actions.delete, + image: Asset.Circles.forbidden20.image + ) { [weak notificationView] _ in + guard let notificationView = notificationView, let delegate = notificationView.delegate else { return false } + delegate.notificationView(notificationView, rejectFollowRequestButtonDidPressed: notificationView.rejectFollowRequestButton) + return true + } + ) + } + + notificationView.notificationActions = actions + } + .store(in: &disposeBag) } private func bindAuthorMenu(notificationView: NotificationView) { @@ -146,7 +218,9 @@ extension NotificationView.ViewModel { isMyself: isMyself, isBookmarking: false // no bookmark action display for notification item ) - notificationView.menuButton.menu = notificationView.setupAuthorMenu(menuContext: menuContext) + let (menu, actions) = notificationView.setupAuthorMenu(menuContext: menuContext) + notificationView.menuButton.menu = menu + notificationView.authorActions = actions notificationView.menuButton.showsMenuAsPrimaryAction = true notificationView.menuButton.isHidden = menuContext.isMyself @@ -206,5 +280,5 @@ extension NotificationView.ViewModel { } .store(in: &disposeBag) } - + } diff --git a/MastodonSDK/Sources/MastodonUI/View/Content/NotificationView.swift b/MastodonSDK/Sources/MastodonUI/View/Content/NotificationView.swift index e6263dda9..ddc1add5c 100644 --- a/MastodonSDK/Sources/MastodonUI/View/Content/NotificationView.swift +++ b/MastodonSDK/Sources/MastodonUI/View/Content/NotificationView.swift @@ -46,7 +46,10 @@ public final class NotificationView: UIView { var _disposeBag = Set() public var disposeBag = Set() - + + var notificationActions = [UIAccessibilityCustomAction]() + var authorActions = [UIAccessibilityCustomAction]() + public private(set) lazy var viewModel: ViewModel = { let viewModel = ViewModel() viewModel.bind(notificationView: self) @@ -372,6 +375,30 @@ extension NotificationView { statusView.delegate = self quoteStatusView.delegate = self + + isAccessibilityElement = true + } +} + +extension NotificationView { + public override var accessibilityElements: [Any]? { + get { [] } + set {} + } + + public override var accessibilityCustomActions: [UIAccessibilityCustomAction]? { + get { + var actions = notificationActions + actions += authorActions + if !statusView.isHidden { + actions += statusView.accessibilityCustomActions ?? [] + } + if !quoteStatusViewContainerView.isHidden { + actions += quoteStatusView.accessibilityCustomActions ?? [] + } + return actions + } + set {} } } @@ -430,7 +457,7 @@ extension NotificationView: AdaptiveContainerView { extension NotificationView { public typealias AuthorMenuContext = StatusAuthorView.AuthorMenuContext - public func setupAuthorMenu(menuContext: AuthorMenuContext) -> UIMenu { + public func setupAuthorMenu(menuContext: AuthorMenuContext) -> (UIMenu, [UIAccessibilityCustomAction]) { var actions: [MastodonMenu.Action] = [] actions = [ @@ -456,8 +483,13 @@ extension NotificationView { actions: actions, delegate: self ) - - return menu + + let accessibilityActions = MastodonMenu.setupAccessibilityActions( + actions: actions, + delegate: self + ) + + return (menu, accessibilityActions) } } diff --git a/MastodonSDK/Sources/MastodonUI/View/Content/StatusView+ViewModel.swift b/MastodonSDK/Sources/MastodonUI/View/Content/StatusView+ViewModel.swift index 1c09cd3f3..597239812 100644 --- a/MastodonSDK/Sources/MastodonUI/View/Content/StatusView+ViewModel.swift +++ b/MastodonSDK/Sources/MastodonUI/View/Content/StatusView+ViewModel.swift @@ -239,12 +239,11 @@ extension StatusView.ViewModel { } .store(in: &disposeBag) // username - let usernamePublisher = $authorUsername + $authorUsername .map { text -> String in guard let text = text else { return "" } return "@\(text)" } - usernamePublisher .sink { username in let metaContent = PlaintextMetaContent(string: username) authorView.authorUsernameLabel.configure(content: metaContent) @@ -271,18 +270,6 @@ extension StatusView.ViewModel { authorView.dateLabel.configure(content: PlaintextMetaContent(string: text)) } .store(in: &disposeBag) - - // accessibility label - Publishers.CombineLatest4($authorName, usernamePublisher, $timestampText, $timestamp) - .map { name, username, timestampText, timestamp in - let formatter = DateFormatter() - formatter.dateStyle = .medium - formatter.timeStyle = .short - let longTimestamp = timestamp.map { formatter.string(from: $0) } ?? "" - return "\(name?.string ?? "") \(username), \(timestampText). \(longTimestamp)" - } - .assign(to: \.accessibilityLabel, on: authorView) - .store(in: &disposeBag) } private func bindContent(statusView: StatusView) { @@ -635,7 +622,7 @@ extension StatusView.ViewModel { } private func bindAccessibility(statusView: StatusView) { - let authorAccessibilityLabel = Publishers.CombineLatest3( + let shortAuthorAccessibilityLabel = Publishers.CombineLatest3( $header, $authorName, $timestampText @@ -645,19 +632,56 @@ extension StatusView.ViewModel { switch header { case .none: - break + strings.append(authorName?.string) case .reply(let info): + strings.append(authorName?.string) strings.append(info.header.string) case .repost(let info): strings.append(info.header.string) + strings.append(authorName?.string) } - strings.append(authorName?.string) strings.append(timestamp) return strings.compactMap { $0 }.joined(separator: ", ") } - + + let longTimestampFormatter = DateFormatter() + longTimestampFormatter.dateStyle = .medium + longTimestampFormatter.timeStyle = .short + let longTimestampLabel = Publishers.CombineLatest( + $timestampText, + $timestamp.map { timestamp in + if let timestamp { + return longTimestampFormatter.string(from: timestamp) + } + return "" + } + ) + .map { timestampText, longTimestamp in + "\(timestampText). \(longTimestamp)" + } + + Publishers.CombineLatest4( + $header, + $authorName, + $authorUsername, + longTimestampLabel + ) + .map { header, name, username, timestamp in + let nameAndUsername = "\(name?.string ?? "") @\(username ?? "")" + switch header { + case .none: + return "\(nameAndUsername), \(timestamp)" + case .repost(info: let info): + return "\(info.header.string) \(nameAndUsername), \(timestamp)" + case .reply(info: let info): + return "\(nameAndUsername) \(info.header.string), \(timestamp)" + } + } + .assign(to: \.accessibilityLabel, on: statusView.authorView) + .store(in: &disposeBag) + let contentAccessibilityLabel = Publishers.CombineLatest3( $isContentReveal, $spoilerContent, @@ -695,8 +719,8 @@ extension StatusView.ViewModel { statusView.spoilerOverlayView.accessibilityLabel = contentAccessibilityLabel } .store(in: &disposeBag) - - let meidaAccessibilityLabel = $mediaViewConfigurations + + let mediaAccessibilityLabel = $mediaViewConfigurations .map { configurations -> String? in let count = configurations.count return L10n.Plural.Count.media(count) @@ -705,18 +729,18 @@ extension StatusView.ViewModel { // TODO: Toolbar Publishers.CombineLatest3( - authorAccessibilityLabel, + shortAuthorAccessibilityLabel, contentAccessibilityLabel, - meidaAccessibilityLabel + mediaAccessibilityLabel ) .map { author, content, media in - let group = [ - author, - content, - media - ] - - return group + var labels: [String?] = [content, media] + + if statusView.style != .notification { + labels.insert(author, at: 0) + } + + return labels .compactMap { $0 } .joined(separator: ", ") } diff --git a/MastodonSDK/Sources/MastodonUI/View/Content/StatusView.swift b/MastodonSDK/Sources/MastodonUI/View/Content/StatusView.swift index 4943746b1..30f62eaf7 100644 --- a/MastodonSDK/Sources/MastodonUI/View/Content/StatusView.swift +++ b/MastodonSDK/Sources/MastodonUI/View/Content/StatusView.swift @@ -241,6 +241,7 @@ extension StatusView { // header headerIconImageView.isUserInteractionEnabled = false headerInfoLabel.isUserInteractionEnabled = false + headerInfoLabel.isAccessibilityElement = false let headerTapGestureRecognizer = UITapGestureRecognizer.singleTapGestureRecognizer headerTapGestureRecognizer.addTarget(self, action: #selector(StatusView.headerDidPressed(_:))) headerContainerView.addGestureRecognizer(headerTapGestureRecognizer)