diff --git a/.arkana.yml b/.arkana.yml index 2fd8f1ab4..7865ceacf 100644 --- a/.arkana.yml +++ b/.arkana.yml @@ -13,4 +13,5 @@ global_secrets: # nothing environment_secrets: # Will lookup for Debug and Release env vars (assuming no flavor was declared) + # Mastodon Push Notification Endpoint - NotificationEndpoint diff --git a/.gitignore b/.gitignore index 6f4802cab..2d787576b 100644 --- a/.gitignore +++ b/.gitignore @@ -122,4 +122,7 @@ xcuserdata # Localization/StringsConvertor/input Localization/StringsConvertor/output -.DS_Store \ No newline at end of file +.DS_Store + +env/**/** +!env/.env \ No newline at end of file diff --git a/Documentation/Acknowledgments.md b/Documentation/Acknowledgments.md index 154514f5f..3e9a2d7b4 100644 --- a/Documentation/Acknowledgments.md +++ b/Documentation/Acknowledgments.md @@ -7,8 +7,6 @@ - [CommonOSLog](https://github.com/mainasuk/CommonOSLog) - [CryptoSwift](https://github.com/krzyzanowskim/CryptoSwift) - [DateToolSwift](https://github.com/MatthewYork/DateTools) -- [DiffableDataSources](https://github.com/ra1028/DiffableDataSources) -- [DifferenceKit](https://github.com/ra1028/DifferenceKit) - [FLAnimatedImage](https://github.com/Flipboard/FLAnimatedImage) - [FLEX](https://github.com/FLEXTool/FLEX) - [FPSIndicator](https://github.com/MainasuK/FPSIndicator) diff --git a/Documentation/Setup.md b/Documentation/Setup.md index e0e2eb3f2..bdf2a2c77 100644 --- a/Documentation/Setup.md +++ b/Documentation/Setup.md @@ -50,10 +50,11 @@ bundle install ```zsh # make a clean build bundle install +bundle exec pod clean -# setup notification endpoint +# setup arkana # please check the `.env.example` to create your's or use the empty example directly -bundle exec arkana -e `` +bundle exec arkana -e ./env/.env # clean pods bundle exec pod clean @@ -65,13 +66,14 @@ bundle exec pod install --repo-update open Mastodon.xcworkspace ``` -The CocoaPods-Key plugin will request the push notification endpoint. You can fufill the empty string and set it later. To setup the push notification. Please check section `Push Notification` below. +The Arkana plugin will setup the push notification endpoint. You can use the empty template from `./env/.env` or use your own `.env` file. To setup the push notification. Please check section `Push Notification` below. The app requires the `App Group` capability. To make sure it works for your developer membership. Please check [AppSecret.swift](../MastodonSDK/Sources/MastodonCore/AppSecret.swift) file and set another unique `groupID` and update `App Group` settings. #### Push Notification (Optional) -The app is compatible with [toot-relay](https://github.com/DagAgren/toot-relay) APNs. You can set your push notification endpoint via Arkana. There are one endpoint: -- NotificationEndpoint: (e.g. https:///relay-to/production) +The app is compatible with [toot-relay](https://github.com/DagAgren/toot-relay) APNs. You can set your push notification endpoint via Arkana. There are two endpoints: +- NotificationEndpointDebug: for `DEBUG` usage. e.g. `https:///relay-to/development` +- NotificationEndpointRelease: for `RELEASE` usage. e.g. `https:///relay-to/production` Please check the [Establishing a Certificate-Based Connection to APNs ](https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/establishing_a_certificate-based_connection_to_apns) document to generate the certificate and exports the p12 file. @@ -87,4 +89,4 @@ Please check and set the `notification.Topic` to the app BundleID in [toot-relay ## What's next -We welcome contributions! And if you have an interest to contribute codes. Here is a document that describes the app architecture and what's tech stack it uses. \ No newline at end of file +We welcome contributions! And if you have an interest to contribute codes. Here is a document that describes the app architecture and what's tech stack it uses. diff --git a/Gemfile b/Gemfile index 510d3ccff..7ecafafc1 100644 --- a/Gemfile +++ b/Gemfile @@ -3,3 +3,5 @@ source "https://rubygems.org" gem 'arkana' gem "cocoapods" gem "cocoapods-clean" +gem "xcpretty" + diff --git a/Gemfile.lock b/Gemfile.lock index 4f4903ed5..873f725e5 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -3,14 +3,14 @@ GEM specs: CFPropertyList (3.0.5) rexml - activesupport (6.1.5.1) + activesupport (6.1.7) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 1.6, < 2) minitest (>= 5.1) tzinfo (~> 2.0) zeitwerk (~> 2.3) - addressable (2.8.0) - public_suffix (>= 2.0.2, < 5.0) + addressable (2.8.1) + public_suffix (>= 2.0.2, < 6.0) algoliasearch (1.27.5) httpclient (~> 2.8, >= 2.8.3) json (>= 1.5.1) @@ -70,38 +70,42 @@ GEM fuzzy_match (2.0.4) gh_inspector (1.1.3) httpclient (2.8.3) - i18n (1.10.0) + i18n (1.12.0) concurrent-ruby (~> 1.0) - json (2.6.1) - minitest (5.15.0) + json (2.6.2) + minitest (5.16.3) molinillo (0.8.0) nanaimo (0.3.0) nap (1.1.0) netrc (0.11.0) public_suffix (4.0.7) rexml (3.2.5) + rouge (2.0.7) ruby-macho (2.5.1) typhoeus (1.4.0) ethon (>= 0.9.0) - tzinfo (2.0.4) + tzinfo (2.0.5) concurrent-ruby (~> 1.0) - xcodeproj (1.21.0) + xcodeproj (1.22.0) CFPropertyList (>= 2.3.3, < 4.0) atomos (~> 0.1.3) claide (>= 1.0.2, < 2.0) colored2 (~> 3.1) nanaimo (~> 0.3.0) rexml (~> 3.2.4) + xcpretty (0.3.0) + rouge (~> 2.0.7) yaml (0.2.0) - zeitwerk (2.5.4) + zeitwerk (2.6.3) PLATFORMS - ruby + arm64-darwin-21 DEPENDENCIES arkana cocoapods cocoapods-clean + xcpretty BUNDLED WITH 2.3.17 diff --git a/Localization/StringsConvertor/input/ar.lproj/Localizable.stringsdict b/Localization/StringsConvertor/input/ar.lproj/Localizable.stringsdict index 197897e8e..862d98184 100644 --- a/Localization/StringsConvertor/input/ar.lproj/Localizable.stringsdict +++ b/Localization/StringsConvertor/input/ar.lproj/Localizable.stringsdict @@ -224,17 +224,17 @@ NSStringFormatValueTypeKey ld zero - لا إعاد تدوين + لَا إعادَةُ تَدوين one - إعادةُ تدوينٍ واحِدة + إعادَةُ تَدوينٍ واحِدَة two - إعادتا تدوين + إعادَتَا تَدوين few - %ld إعاداتِ تدوين + %ld إعادَاتِ تَدوين many - %ld إعادةٍ للتدوين + %ld إعادَةٍ لِلتَّدوين other - %ld إعادة تدوين + %ld إعادَة تَدوين plural.count.reply diff --git a/Localization/StringsConvertor/input/fr.lproj/app.json b/Localization/StringsConvertor/input/fr.lproj/app.json index dcb8750c4..6ae2ebb2b 100644 --- a/Localization/StringsConvertor/input/fr.lproj/app.json +++ b/Localization/StringsConvertor/input/fr.lproj/app.json @@ -348,7 +348,7 @@ "Publishing": "Publication en cours ...", "accessibility": { "logo_label": "Bouton logo", - "logo_hint": "Tap to scroll to top and tap again to previous location" + "logo_hint": "Appuyez pour faire défiler vers le haut et appuyez à nouveau vers l'emplacement précédent" } } }, @@ -546,10 +546,10 @@ "show_mentions": "Afficher les mentions" }, "follow_request": { - "accept": "Accept", - "accepted": "Accepted", - "reject": "reject", - "rejected": "Rejected" + "accept": "Accepter", + "accepted": "Accepté", + "reject": "rejeter", + "rejected": "Rejeté" } }, "thread": { diff --git a/Localization/StringsConvertor/input/kmr.lproj/app.json b/Localization/StringsConvertor/input/kmr.lproj/app.json index 418c747f2..df87daffe 100644 --- a/Localization/StringsConvertor/input/kmr.lproj/app.json +++ b/Localization/StringsConvertor/input/kmr.lproj/app.json @@ -546,10 +546,10 @@ "show_mentions": "Qalkirinan nîşan bike" }, "follow_request": { - "accept": "Accept", - "accepted": "Accepted", - "reject": "reject", - "rejected": "Rejected" + "accept": "Bipejirîne", + "accepted": "Pejirandî", + "reject": "nepejirîne", + "rejected": "Nepejirandî" } }, "thread": { diff --git a/Localization/StringsConvertor/input/th.lproj/app.json b/Localization/StringsConvertor/input/th.lproj/app.json index 9c2a9f4f7..85d2d52ae 100644 --- a/Localization/StringsConvertor/input/th.lproj/app.json +++ b/Localization/StringsConvertor/input/th.lproj/app.json @@ -546,10 +546,10 @@ "show_mentions": "แสดงการกล่าวถึง" }, "follow_request": { - "accept": "Accept", - "accepted": "Accepted", - "reject": "reject", - "rejected": "Rejected" + "accept": "ยอมรับ", + "accepted": "ยอมรับแล้ว", + "reject": "ปฏิเสธ", + "rejected": "ปฏิเสธแล้ว" } }, "thread": { diff --git a/Mastodon.xcodeproj/project.pbxproj b/Mastodon.xcodeproj/project.pbxproj index 66c28a399..68aae2d4e 100644 --- a/Mastodon.xcodeproj/project.pbxproj +++ b/Mastodon.xcodeproj/project.pbxproj @@ -3866,7 +3866,7 @@ CODE_SIGN_ENTITLEMENTS = Mastodon/Mastodon.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 144; + CURRENT_PROJECT_VERSION = 147; DEVELOPMENT_ASSET_PATHS = "Mastodon/Resources/Preview\\ Assets.xcassets"; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = Mastodon/Info.plist; @@ -3896,7 +3896,7 @@ CODE_SIGN_ENTITLEMENTS = Mastodon/Mastodon.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 144; + CURRENT_PROJECT_VERSION = 147; DEVELOPMENT_ASSET_PATHS = "Mastodon/Resources/Preview\\ Assets.xcassets"; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = Mastodon/Info.plist; @@ -4069,7 +4069,7 @@ CODE_SIGN_ENTITLEMENTS = Mastodon/Mastodon.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 144; + CURRENT_PROJECT_VERSION = 147; DEVELOPMENT_ASSET_PATHS = "Mastodon/Resources/Preview\\ Assets.xcassets"; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = Mastodon/Info.plist; @@ -4136,7 +4136,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = NotificationService/NotificationService.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 144; + CURRENT_PROJECT_VERSION = 147; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = NotificationService/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -4159,7 +4159,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = ShareActionExtension/ShareActionExtension.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 144; + CURRENT_PROJECT_VERSION = 147; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = ShareActionExtension/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -4183,7 +4183,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = MastodonIntent/MastodonIntent.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 144; + CURRENT_PROJECT_VERSION = 147; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = MastodonIntent/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -4207,7 +4207,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = MastodonIntent/MastodonIntent.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 144; + CURRENT_PROJECT_VERSION = 147; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = MastodonIntent/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -4231,7 +4231,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = MastodonIntent/MastodonIntent.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 144; + CURRENT_PROJECT_VERSION = 147; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = MastodonIntent/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -4255,7 +4255,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = ShareActionExtension/ShareActionExtension.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 144; + CURRENT_PROJECT_VERSION = 147; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = ShareActionExtension/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -4279,7 +4279,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = ShareActionExtension/ShareActionExtension.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 144; + CURRENT_PROJECT_VERSION = 147; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = ShareActionExtension/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -4366,7 +4366,7 @@ CODE_SIGN_ENTITLEMENTS = Mastodon/Mastodon.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 144; + CURRENT_PROJECT_VERSION = 147; DEVELOPMENT_ASSET_PATHS = "Mastodon/Resources/Preview\\ Assets.xcassets"; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = Mastodon/Info.plist; @@ -4426,13 +4426,43 @@ }; name = "Release Snapshot"; }; + DBEB19E527E4658E00B0E80E /* Release Snapshot */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 3E08A432F40BA7B9CAA9DB68 /* Pods-AppShared.release snapshot.xcconfig */; + buildSettings = { + APPLICATION_EXTENSION_API_ONLY = YES; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 147; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 5Z4GVSS33P; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 147; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = AppShared/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app.AppShared; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = "Release Snapshot"; + }; DBEB19E627E4658E00B0E80E /* Release Snapshot */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = NotificationService/NotificationService.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 144; + CURRENT_PROJECT_VERSION = 147; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = NotificationService/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -4455,7 +4485,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = ShareActionExtension/ShareActionExtension.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 144; + CURRENT_PROJECT_VERSION = 147; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = ShareActionExtension/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -4479,7 +4509,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = MastodonIntent/MastodonIntent.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 144; + CURRENT_PROJECT_VERSION = 147; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = MastodonIntent/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -4504,7 +4534,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = NotificationService/NotificationService.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 144; + CURRENT_PROJECT_VERSION = 147; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = NotificationService/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -4528,7 +4558,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = NotificationService/NotificationService.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 144; + CURRENT_PROJECT_VERSION = 147; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = NotificationService/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( diff --git a/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist b/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist index 33eda54f1..de5069e58 100644 --- a/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist @@ -99,6 +99,11 @@ orderHint 0 + MastodonIntent.xcscheme_^#shared#^_ + + orderHint + 20 + MastodonIntents.xcscheme_^#shared#^_ orderHint @@ -112,12 +117,12 @@ NotificationService.xcscheme_^#shared#^_ orderHint - 18 + 24 ShareActionExtension.xcscheme_^#shared#^_ orderHint - 17 + 25 SuppressBuildableAutocreation diff --git a/Mastodon/Coordinator/SceneCoordinator.swift b/Mastodon/Coordinator/SceneCoordinator.swift index 58323cc4e..8a0825969 100644 --- a/Mastodon/Coordinator/SceneCoordinator.swift +++ b/Mastodon/Coordinator/SceneCoordinator.swift @@ -20,7 +20,7 @@ final public class SceneCoordinator { private weak var scene: UIScene! private weak var sceneDelegate: SceneDelegate! - private weak var appContext: AppContext! + private(set) weak var appContext: AppContext! private(set) var authContext: AuthContext? diff --git a/Mastodon/Info.plist b/Mastodon/Info.plist index 31b425322..cfdbb923d 100644 --- a/Mastodon/Info.plist +++ b/Mastodon/Info.plist @@ -2,19 +2,6 @@ - NSAppTransportSecurity - - NSExceptionDomains - - onion - - NSExceptionAllowsInsecureHTTPLoads - - NSIncludesSubdomains - - - - CADisableMinimumFrameDurationOnPhone CFBundleDevelopmentRegion @@ -30,7 +17,7 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 1.4.5 + 1.4.6 CFBundleURLTypes @@ -43,7 +30,7 @@ CFBundleVersion - 144 + 147 ITSAppUsesNonExemptEncryption LSApplicationQueriesSchemes @@ -59,6 +46,19 @@ LSRequiresIPhoneOS + NSAppTransportSecurity + + NSExceptionDomains + + onion + + NSExceptionAllowsInsecureHTTPLoads + + NSIncludesSubdomains + + + + NSUserActivityTypes SendPostIntent @@ -103,6 +103,10 @@ UIApplicationSupportsIndirectInputEvents + UIBackgroundModes + + remote-notification + UILaunchStoryboardName Main UIMainStoryboardFile diff --git a/Mastodon/Scene/Profile/Header/View/ProfileHeaderView.swift b/Mastodon/Scene/Profile/Header/View/ProfileHeaderView.swift index 9e6e163aa..a2194758b 100644 --- a/Mastodon/Scene/Profile/Header/View/ProfileHeaderView.swift +++ b/Mastodon/Scene/Profile/Header/View/ProfileHeaderView.swift @@ -79,7 +79,7 @@ final class ProfileHeaderView: UIView { let followsYouLabel: UILabel = { let label = UILabel() label.font = UIFont.systemFont(ofSize: 15, weight: .regular) - label.text = "Follows You" // TODO: i18n + label.text = L10n.Scene.Profile.Header.followsYou return label }() let followsYouMaskView = UIView() diff --git a/Mastodon/Supporting Files/AppDelegate.swift b/Mastodon/Supporting Files/AppDelegate.swift index 7336125b8..3bf38f6da 100644 --- a/Mastodon/Supporting Files/AppDelegate.swift +++ b/Mastodon/Supporting Files/AppDelegate.swift @@ -106,6 +106,14 @@ extension AppDelegate: UNUserNotificationCenterDelegate { completionHandler([.sound]) } + + // notification present in the background (or resume from background) + func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any]) async -> UIBackgroundFetchResult { + let shortcutItems = try? await appContext.notificationService.unreadApplicationShortcutItems() + UIApplication.shared.shortcutItems = shortcutItems + return .noData + } + // response to user action for notification (e.g. redirect to post) func userNotificationCenter( _ center: UNUserNotificationCenter, diff --git a/Mastodon/Supporting Files/SceneDelegate.swift b/Mastodon/Supporting Files/SceneDelegate.swift index a2dd1625a..d813f8ffd 100644 --- a/Mastodon/Supporting Files/SceneDelegate.swift +++ b/Mastodon/Supporting Files/SceneDelegate.swift @@ -10,6 +10,7 @@ import UIKit import Combine import CoreDataStack import MastodonCore +import MastodonExtension #if PROFILE import FPSIndicator @@ -110,7 +111,9 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { AppContext.shared.statusFilterService.filterUpdatePublisher.send() if let shortcutItem = savedShortCutItem { - _ = handler(shortcutItem: shortcutItem) + Task { + _ = await handler(shortcutItem: shortcutItem) + } savedShortCutItem = nil } } @@ -134,14 +137,43 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { } extension SceneDelegate { - func windowScene(_ windowScene: UIWindowScene, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: @escaping (Bool) -> Void) { - completionHandler(handler(shortcutItem: shortcutItem)) + + func windowScene(_ windowScene: UIWindowScene, performActionFor shortcutItem: UIApplicationShortcutItem) async -> Bool { + return await handler(shortcutItem: shortcutItem) } - private func handler(shortcutItem: UIApplicationShortcutItem) -> Bool { + @MainActor + private func handler(shortcutItem: UIApplicationShortcutItem) async -> Bool { logger.debug("\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): \(shortcutItem.type)") switch shortcutItem.type { + case NotificationService.unreadShortcutItemIdentifier: + guard let coordinator = self.coordinator else { return false } + + guard let accessToken = shortcutItem.userInfo?["accessToken"] as? String else { + assertionFailure() + return false + } + let request = MastodonAuthentication.sortedFetchRequest + request.predicate = MastodonAuthentication.predicate(userAccessToken: accessToken) + request.fetchLimit = 1 + + guard let authentication = try? coordinator.appContext.managedObjectContext.fetch(request).first else { + assertionFailure() + return false + } + + let _isActive = try? await coordinator.appContext.authenticationService.activeMastodonUser( + domain: authentication.domain, + userID: authentication.userID + ) + + guard _isActive == true else { + return false + } + + coordinator.switchToTabBar(tab: .notification) + case "org.joinmastodon.app.new-post": if coordinator?.tabBarController.topMost is ComposeViewController { logger.debug("\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): composing…") @@ -158,6 +190,7 @@ extension SceneDelegate { logger.debug("\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): not authenticated") } } + case "org.joinmastodon.app.search": coordinator?.switchToTabBar(tab: .search) logger.debug("\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): select search tab") @@ -166,6 +199,7 @@ extension SceneDelegate { searchViewController.searchBarTapPublisher.send() logger.debug("\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): trigger search") } + default: assertionFailure() break diff --git a/MastodonIntent/Info.plist b/MastodonIntent/Info.plist index 05a3cc313..3816b660b 100644 --- a/MastodonIntent/Info.plist +++ b/MastodonIntent/Info.plist @@ -17,9 +17,9 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 1.4.5 + 1.4.6 CFBundleVersion - 144 + 147 NSExtension NSExtensionAttributes diff --git a/MastodonSDK/Package.swift b/MastodonSDK/Package.swift index a9c9ab9db..852817c20 100644 --- a/MastodonSDK/Package.swift +++ b/MastodonSDK/Package.swift @@ -73,7 +73,7 @@ let package = Package( .target( name: "MastodonCommon", dependencies: [ - "MastodonExtension" + "MastodonExtension", ] ), .target( diff --git a/MastodonSDK/Sources/MastodonAsset/Generated/Assets.swift b/MastodonSDK/Sources/MastodonAsset/Generated/Assets.swift index f9db9d32f..5cd0059d8 100644 --- a/MastodonSDK/Sources/MastodonAsset/Generated/Assets.swift +++ b/MastodonSDK/Sources/MastodonAsset/Generated/Assets.swift @@ -130,17 +130,17 @@ public enum Asset { } public enum Scene { public enum Compose { + public static let earth = ImageAsset(name: "Scene/Compose/Earth") + public static let mention = ImageAsset(name: "Scene/Compose/Mention") + public static let more = ImageAsset(name: "Scene/Compose/More") + public static let people = ImageAsset(name: "Scene/Compose/People") public static let buttonTint = ColorAsset(name: "Scene/Compose/button.tint") public static let chatWarningFill = ImageAsset(name: "Scene/Compose/chat.warning.fill") public static let chatWarning = ImageAsset(name: "Scene/Compose/chat.warning") - public static let earth = ImageAsset(name: "Scene/Compose/earth") public static let emojiFill = ImageAsset(name: "Scene/Compose/emoji.fill") public static let emoji = ImageAsset(name: "Scene/Compose/emoji") public static let media = ImageAsset(name: "Scene/Compose/media") - public static let mention = ImageAsset(name: "Scene/Compose/mention") - public static let more = ImageAsset(name: "Scene/Compose/more") public static let peopleAdd = ImageAsset(name: "Scene/Compose/people.add") - public static let people = ImageAsset(name: "Scene/Compose/people") public static let pollFill = ImageAsset(name: "Scene/Compose/poll.fill") public static let poll = ImageAsset(name: "Scene/Compose/poll") public static let reorderDot = ImageAsset(name: "Scene/Compose/reorder.dot") diff --git a/MastodonSDK/Sources/MastodonCore/AppSecret.swift b/MastodonSDK/Sources/MastodonCore/AppSecret.swift index 687cb6fca..c409a2761 100644 --- a/MastodonSDK/Sources/MastodonCore/AppSecret.swift +++ b/MastodonSDK/Sources/MastodonCore/AppSecret.swift @@ -37,9 +37,11 @@ public final class AppSecret { init() { #if DEBUG - self.notificationEndpoint = Keys.Debug().notificationEndpoint + let keys = Keys.Debug() + self.notificationEndpoint = keys.notificationEndpoint #else - self.notificationEndpoint = Keys.Release().notificationEndpoint + let keys = Keys.Release() + self.notificationEndpoint = keys.notificationEndpoint #endif } diff --git a/MastodonSDK/Sources/MastodonCore/Service/Notification/NotificationService.swift b/MastodonSDK/Sources/MastodonCore/Service/Notification/NotificationService.swift index 18ff2e508..65d92fc29 100644 --- a/MastodonSDK/Sources/MastodonCore/Service/Notification/NotificationService.swift +++ b/MastodonSDK/Sources/MastodonCore/Service/Notification/NotificationService.swift @@ -12,9 +12,12 @@ import CoreData import CoreDataStack import MastodonSDK import MastodonCommon +import MastodonLocalization public final class NotificationService { + public static let unreadShortcutItemIdentifier = "org.joinmastodon.app.NotificationService.unread-shortcut" + var disposeBag = Set() let workingQueue = DispatchQueue(label: "org.joinmastodon.app.NotificationService.working-queue") @@ -75,6 +78,9 @@ public final class NotificationService { UserDefaults.shared.notificationBadgeCount = count UIApplication.shared.applicationIconBadgeNumber = count + Task { @MainActor in + UIApplication.shared.shortcutItems = try? await self.unreadApplicationShortcutItems() + } self.unreadNotificationCountDidUpdate.send() } @@ -101,6 +107,37 @@ extension NotificationService { } } +extension NotificationService { + public func unreadApplicationShortcutItems() async throws -> [UIApplicationShortcutItem] { + guard let authenticationService = self.authenticationService else { return [] } + let managedObjectContext = authenticationService.managedObjectContext + return try await managedObjectContext.perform { + var items: [UIApplicationShortcutItem] = [] + for object in authenticationService.mastodonAuthentications { + guard let authentication = managedObjectContext.object(with: object.objectID) as? MastodonAuthentication else { continue } + let accessToken = authentication.userAccessToken + let count = UserDefaults.shared.getNotificationCountWithAccessToken(accessToken: accessToken) + guard count > 0 else { continue } + + let title = "@\(authentication.user.acctWithDomain)" + let subtitle = L10n.A11y.Plural.Count.Unread.notification(count) + + let item = UIApplicationShortcutItem( + type: NotificationService.unreadShortcutItemIdentifier, + localizedTitle: title, + localizedSubtitle: subtitle, + icon: nil, + userInfo: [ + "accessToken": accessToken as NSSecureCoding + ] + ) + items.append(item) + } + return items + } + } +} + extension NotificationService { func dequeueNotificationViewModel( diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/ar.lproj/Localizable.stringsdict b/MastodonSDK/Sources/MastodonLocalization/Resources/ar.lproj/Localizable.stringsdict index 197897e8e..862d98184 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/ar.lproj/Localizable.stringsdict +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/ar.lproj/Localizable.stringsdict @@ -224,17 +224,17 @@ NSStringFormatValueTypeKey ld zero - لا إعاد تدوين + لَا إعادَةُ تَدوين one - إعادةُ تدوينٍ واحِدة + إعادَةُ تَدوينٍ واحِدَة two - إعادتا تدوين + إعادَتَا تَدوين few - %ld إعاداتِ تدوين + %ld إعادَاتِ تَدوين many - %ld إعادةٍ للتدوين + %ld إعادَةٍ لِلتَّدوين other - %ld إعادة تدوين + %ld إعادَة تَدوين plural.count.reply diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/fr.lproj/Localizable.strings b/MastodonSDK/Sources/MastodonLocalization/Resources/fr.lproj/Localizable.strings index d0a964b54..5e0b9be61 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/fr.lproj/Localizable.strings +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/fr.lproj/Localizable.strings @@ -216,17 +216,17 @@ téléversé sur Mastodon."; "Scene.Follower.Title" = "abonné·e"; "Scene.Following.Footer" = "Les abonnés issus des autres serveurs ne sont pas affichés."; "Scene.Following.Title" = "following"; -"Scene.HomeTimeline.NavigationBarState.Accessibility.LogoHint" = "Tap to scroll to top and tap again to previous location"; +"Scene.HomeTimeline.NavigationBarState.Accessibility.LogoHint" = "Appuyez pour faire défiler vers le haut et appuyez à nouveau vers l'emplacement précédent"; "Scene.HomeTimeline.NavigationBarState.Accessibility.LogoLabel" = "Bouton logo"; "Scene.HomeTimeline.NavigationBarState.NewPosts" = "Voir les nouvelles publications"; "Scene.HomeTimeline.NavigationBarState.Offline" = "Hors ligne"; "Scene.HomeTimeline.NavigationBarState.Published" = "Publié!"; "Scene.HomeTimeline.NavigationBarState.Publishing" = "Publication en cours ..."; "Scene.HomeTimeline.Title" = "Accueil"; -"Scene.Notification.FollowRequest.Accept" = "Accept"; -"Scene.Notification.FollowRequest.Accepted" = "Accepted"; -"Scene.Notification.FollowRequest.Reject" = "reject"; -"Scene.Notification.FollowRequest.Rejected" = "Rejected"; +"Scene.Notification.FollowRequest.Accept" = "Accepter"; +"Scene.Notification.FollowRequest.Accepted" = "Accepté"; +"Scene.Notification.FollowRequest.Reject" = "rejeter"; +"Scene.Notification.FollowRequest.Rejected" = "Rejeté"; "Scene.Notification.Keyobard.ShowEverything" = "Tout Afficher"; "Scene.Notification.Keyobard.ShowMentions" = "Afficher les mentions"; "Scene.Notification.NotificationDescription.FavoritedYourPost" = "a ajouté votre message à ses favoris"; diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/ku.lproj/Localizable.strings b/MastodonSDK/Sources/MastodonLocalization/Resources/ku.lproj/Localizable.strings index a77923674..6c323adfe 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/ku.lproj/Localizable.strings +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/ku.lproj/Localizable.strings @@ -224,10 +224,10 @@ girêdanê bitikne da ku ajimêra xwe bidî piştrastkirin."; "Scene.HomeTimeline.NavigationBarState.Published" = "Hate weşandin!"; "Scene.HomeTimeline.NavigationBarState.Publishing" = "Şandî tê weşandin..."; "Scene.HomeTimeline.Title" = "Serrûpel"; -"Scene.Notification.FollowRequest.Accept" = "Accept"; -"Scene.Notification.FollowRequest.Accepted" = "Accepted"; -"Scene.Notification.FollowRequest.Reject" = "reject"; -"Scene.Notification.FollowRequest.Rejected" = "Rejected"; +"Scene.Notification.FollowRequest.Accept" = "Bipejirîne"; +"Scene.Notification.FollowRequest.Accepted" = "Pejirandî"; +"Scene.Notification.FollowRequest.Reject" = "nepejirîne"; +"Scene.Notification.FollowRequest.Rejected" = "Nepejirandî"; "Scene.Notification.Keyobard.ShowEverything" = "Her tiştî nîşan bide"; "Scene.Notification.Keyobard.ShowMentions" = "Qalkirinan nîşan bike"; "Scene.Notification.NotificationDescription.FavoritedYourPost" = "şandiya te hez kir"; diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/th.lproj/Localizable.strings b/MastodonSDK/Sources/MastodonLocalization/Resources/th.lproj/Localizable.strings index 2b67fa50d..f29e08d82 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/th.lproj/Localizable.strings +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/th.lproj/Localizable.strings @@ -223,10 +223,10 @@ "Scene.HomeTimeline.NavigationBarState.Published" = "เผยแพร่แล้ว!"; "Scene.HomeTimeline.NavigationBarState.Publishing" = "กำลังเผยแพร่โพสต์..."; "Scene.HomeTimeline.Title" = "หน้าแรก"; -"Scene.Notification.FollowRequest.Accept" = "Accept"; -"Scene.Notification.FollowRequest.Accepted" = "Accepted"; -"Scene.Notification.FollowRequest.Reject" = "reject"; -"Scene.Notification.FollowRequest.Rejected" = "Rejected"; +"Scene.Notification.FollowRequest.Accept" = "ยอมรับ"; +"Scene.Notification.FollowRequest.Accepted" = "ยอมรับแล้ว"; +"Scene.Notification.FollowRequest.Reject" = "ปฏิเสธ"; +"Scene.Notification.FollowRequest.Rejected" = "ปฏิเสธแล้ว"; "Scene.Notification.Keyobard.ShowEverything" = "แสดงทุกอย่าง"; "Scene.Notification.Keyobard.ShowMentions" = "แสดงการกล่าวถึง"; "Scene.Notification.NotificationDescription.FavoritedYourPost" = "ได้ชื่นชอบโพสต์ของคุณ"; diff --git a/MastodonTests/Info.plist b/MastodonTests/Info.plist index 21baf4a3e..9f09d489c 100644 --- a/MastodonTests/Info.plist +++ b/MastodonTests/Info.plist @@ -15,8 +15,8 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 1.4.5 + 1.4.6 CFBundleVersion - 144 + 147 diff --git a/MastodonUITests/Info.plist b/MastodonUITests/Info.plist index 21baf4a3e..9f09d489c 100644 --- a/MastodonUITests/Info.plist +++ b/MastodonUITests/Info.plist @@ -15,8 +15,8 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 1.4.5 + 1.4.6 CFBundleVersion - 144 + 147 diff --git a/NotificationService/Info.plist b/NotificationService/Info.plist index 1361dc875..854a8a529 100644 --- a/NotificationService/Info.plist +++ b/NotificationService/Info.plist @@ -17,9 +17,9 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 1.4.5 + 1.4.6 CFBundleVersion - 144 + 147 NSExtension NSExtensionPointIdentifier diff --git a/ShareActionExtension/Info.plist b/ShareActionExtension/Info.plist index 18b7be8a4..4372c5048 100644 --- a/ShareActionExtension/Info.plist +++ b/ShareActionExtension/Info.plist @@ -17,9 +17,9 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 1.4.5 + 1.4.6 CFBundleVersion - 144 + 147 NSExtension NSExtensionAttributes diff --git a/ci_scripts/ci_post_clone.sh b/ci_scripts/ci_post_clone.sh new file mode 100755 index 000000000..75605f7b7 --- /dev/null +++ b/ci_scripts/ci_post_clone.sh @@ -0,0 +1,36 @@ +#!/bin/zsh + +# Xcode Cloud scripts + +set -xeu +set -o pipefail + +# list hardware +system_profiler SPSoftwareDataType SPHardwareDataType + +echo $PWD +cd $CI_WORKSPACE +echo $PWD + +# install ruby from homebrew +brew install ruby +echo 'export PATH="/Users/local/Homebrew/opt/ruby/bin:$PATH"' >> ~/.zshrc +source ~/.zshrc + +ruby --version +which gem + +# workaround default installation location cannot access without sudo problem +echo 'export GEM_HOME=$HOME/gems' >>~/.bash_profile +echo 'export PATH=$HOME/gems/bin:$PATH' >>~/.bash_profile +export GEM_HOME=$HOME/gems +export PATH="$GEM_HOME/bin:$PATH" + +# install bundle gem +gem install bundler --install-dir $GEM_HOME + +# setup gems +bundle install + +bundle exec arkana +bundle exec pod install diff --git a/env/.env b/env/.env new file mode 100644 index 000000000..2458052ab --- /dev/null +++ b/env/.env @@ -0,0 +1,3 @@ +# Required +NotificationEndpointDebug="" +NotificationEndpointRelease="" \ No newline at end of file