Merge branch 'release-1.4.6' into feature/package-refactor

# Conflicts:
#	.arkana.yml
#	AppShared/Info.plist
#	Documentation/Setup.md
#	Gemfile
#	Gemfile.lock
#	Mastodon.xcodeproj/project.pbxproj
#	Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist
#	Mastodon.xcworkspace/xcshareddata/swiftpm/Package.resolved
#	MastodonSDK/Package.swift
#	MastodonSDK/Sources/MastodonCore/AppSecret.swift
#	MastodonSDK/Sources/MastodonCore/Service/Notification/NotificationService.swift
#	Podfile
#	Podfile.lock
This commit is contained in:
CMK 2022-11-01 14:14:00 +08:00
commit 060aec6bcb
32 changed files with 282 additions and 113 deletions

View File

@ -13,4 +13,5 @@ global_secrets:
# nothing
environment_secrets:
# Will lookup for <Key>Debug and <Key>Release env vars (assuming no flavor was declared)
# Mastodon Push Notification Endpoint
- NotificationEndpoint

5
.gitignore vendored
View File

@ -122,4 +122,7 @@ xcuserdata
# Localization/StringsConvertor/input
Localization/StringsConvertor/output
.DS_Store
.DS_Store
env/**/**
!env/.env

View File

@ -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)

View File

@ -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 `<your-dot-env-file-path>`
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://<your-domain>/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://<your.domin>/relay-to/development`
- NotificationEndpointRelease: for `RELEASE` usage. e.g. `https://<your.domin>/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.
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.

View File

@ -3,3 +3,5 @@ source "https://rubygems.org"
gem 'arkana'
gem "cocoapods"
gem "cocoapods-clean"
gem "xcpretty"

View File

@ -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

View File

@ -224,17 +224,17 @@
<key>NSStringFormatValueTypeKey</key>
<string>ld</string>
<key>zero</key>
<string>لا إعاد تدوين</string>
<string>لَا إعادَةُ تَدوين</string>
<key>one</key>
<string>إعادةُ تدوينٍ واحِدة</string>
<string>إعادَةُ تَدوينٍ واحِدَة</string>
<key>two</key>
<string>إعادتا تدوين</string>
<string>إعادَتَا تَدوين</string>
<key>few</key>
<string>%ld إعاداتِ تدوين</string>
<string>%ld إعادَاتِ تَدوين</string>
<key>many</key>
<string>%ld إعادةٍ للتدوين</string>
<string>%ld إعادَةٍ لِلتَّدوين</string>
<key>other</key>
<string>%ld إعادة تدوين</string>
<string>%ld إعادَة تَدوين</string>
</dict>
</dict>
<key>plural.count.reply</key>

View File

@ -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": "Reje"
}
},
"thread": {

View File

@ -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": {

View File

@ -546,10 +546,10 @@
"show_mentions": "แสดงการกล่าวถึง"
},
"follow_request": {
"accept": "Accept",
"accepted": "Accepted",
"reject": "reject",
"rejected": "Rejected"
"accept": "ยอมรับ",
"accepted": "ยอมรับแล้ว",
"reject": "ปฏิเสธ",
"rejected": "ปฏิเสธแล้ว"
}
},
"thread": {

View File

@ -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 = (

View File

@ -99,6 +99,11 @@
<key>orderHint</key>
<integer>0</integer>
</dict>
<key>MastodonIntent.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>20</integer>
</dict>
<key>MastodonIntents.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
@ -112,12 +117,12 @@
<key>NotificationService.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>18</integer>
<integer>24</integer>
</dict>
<key>ShareActionExtension.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>17</integer>
<integer>25</integer>
</dict>
</dict>
<key>SuppressBuildableAutocreation</key>

View File

@ -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?

View File

@ -2,19 +2,6 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSExceptionDomains</key>
<dict>
<key>onion</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
<key>NSIncludesSubdomains</key>
<true/>
</dict>
</dict>
</dict>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>CFBundleDevelopmentRegion</key>
@ -30,7 +17,7 @@
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>1.4.5</string>
<string>1.4.6</string>
<key>CFBundleURLTypes</key>
<array>
<dict>
@ -43,7 +30,7 @@
</dict>
</array>
<key>CFBundleVersion</key>
<string>144</string>
<string>147</string>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>LSApplicationQueriesSchemes</key>
@ -59,6 +46,19 @@
</array>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSExceptionDomains</key>
<dict>
<key>onion</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
<key>NSIncludesSubdomains</key>
<true/>
</dict>
</dict>
</dict>
<key>NSUserActivityTypes</key>
<array>
<string>SendPostIntent</string>
@ -103,6 +103,10 @@
</array>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>UIBackgroundModes</key>
<array>
<string>remote-notification</string>
</array>
<key>UILaunchStoryboardName</key>
<string>Main</string>
<key>UIMainStoryboardFile</key>

View File

@ -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()

View File

@ -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,

View File

@ -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

View File

@ -17,9 +17,9 @@
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>1.4.5</string>
<string>1.4.6</string>
<key>CFBundleVersion</key>
<string>144</string>
<string>147</string>
<key>NSExtension</key>
<dict>
<key>NSExtensionAttributes</key>

View File

@ -73,7 +73,7 @@ let package = Package(
.target(
name: "MastodonCommon",
dependencies: [
"MastodonExtension"
"MastodonExtension",
]
),
.target(

View File

@ -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")

View File

@ -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
}

View File

@ -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<AnyCancellable>()
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(

View File

@ -224,17 +224,17 @@
<key>NSStringFormatValueTypeKey</key>
<string>ld</string>
<key>zero</key>
<string>لا إعاد تدوين</string>
<string>لَا إعادَةُ تَدوين</string>
<key>one</key>
<string>إعادةُ تدوينٍ واحِدة</string>
<string>إعادَةُ تَدوينٍ واحِدَة</string>
<key>two</key>
<string>إعادتا تدوين</string>
<string>إعادَتَا تَدوين</string>
<key>few</key>
<string>%ld إعاداتِ تدوين</string>
<string>%ld إعادَاتِ تَدوين</string>
<key>many</key>
<string>%ld إعادةٍ للتدوين</string>
<string>%ld إعادَةٍ لِلتَّدوين</string>
<key>other</key>
<string>%ld إعادة تدوين</string>
<string>%ld إعادَة تَدوين</string>
</dict>
</dict>
<key>plural.count.reply</key>

View File

@ -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" = "Reje";
"Scene.Notification.Keyobard.ShowEverything" = "Tout Afficher";
"Scene.Notification.Keyobard.ShowMentions" = "Afficher les mentions";
"Scene.Notification.NotificationDescription.FavoritedYourPost" = "a ajouté votre message à ses favoris";

View File

@ -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";

View File

@ -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" = "ได้ชื่นชอบโพสต์ของคุณ";

View File

@ -15,8 +15,8 @@
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>1.4.5</string>
<string>1.4.6</string>
<key>CFBundleVersion</key>
<string>144</string>
<string>147</string>
</dict>
</plist>

View File

@ -15,8 +15,8 @@
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>1.4.5</string>
<string>1.4.6</string>
<key>CFBundleVersion</key>
<string>144</string>
<string>147</string>
</dict>
</plist>

View File

@ -17,9 +17,9 @@
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>1.4.5</string>
<string>1.4.6</string>
<key>CFBundleVersion</key>
<string>144</string>
<string>147</string>
<key>NSExtension</key>
<dict>
<key>NSExtensionPointIdentifier</key>

View File

@ -17,9 +17,9 @@
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>1.4.5</string>
<string>1.4.6</string>
<key>CFBundleVersion</key>
<string>144</string>
<string>147</string>
<key>NSExtension</key>
<dict>
<key>NSExtensionAttributes</key>

36
ci_scripts/ci_post_clone.sh Executable file
View File

@ -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

3
env/.env vendored Normal file
View File

@ -0,0 +1,3 @@
# Required
NotificationEndpointDebug=""
NotificationEndpointRelease=""