forked from zelo72/mastodon-ios
commit
389d0971cd
|
@ -8,5 +8,5 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
public enum AppName {
|
public enum AppName {
|
||||||
public static let groupID = "group.org.joinmastodon.mastodon-temp"
|
public static let groupID = "group.org.joinmastodon.app"
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ import Keys
|
||||||
|
|
||||||
public final class AppSecret {
|
public final class AppSecret {
|
||||||
|
|
||||||
public static let keychain = Keychain(service: "org.joinmastodon.Mastodon.keychain", accessGroup: AppName.groupID)
|
public static let keychain = Keychain(service: "org.joinmastodon.app.keychain", accessGroup: AppName.groupID)
|
||||||
|
|
||||||
static let notificationPrivateKeyName = "notification-private-key-base64"
|
static let notificationPrivateKeyName = "notification-private-key-base64"
|
||||||
static let notificationAuthName = "notification-auth-base64"
|
static let notificationAuthName = "notification-auth-base64"
|
||||||
|
|
|
@ -181,11 +181,9 @@
|
||||||
87FFDA5D898A5C42ADCB35E7 /* Pods_Mastodon.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A4ABE34829701A4496C5BB64 /* Pods_Mastodon.framework */; };
|
87FFDA5D898A5C42ADCB35E7 /* Pods_Mastodon.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A4ABE34829701A4496C5BB64 /* Pods_Mastodon.framework */; };
|
||||||
B914FC6B0B8AF18573C0B291 /* Pods_NotificationService.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 374AA339A20E0FAC75BCDA6D /* Pods_NotificationService.framework */; };
|
B914FC6B0B8AF18573C0B291 /* Pods_NotificationService.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 374AA339A20E0FAC75BCDA6D /* Pods_NotificationService.framework */; };
|
||||||
DB00CA972632DDB600A54956 /* CommonOSLog in Frameworks */ = {isa = PBXBuildFile; productRef = DB00CA962632DDB600A54956 /* CommonOSLog */; };
|
DB00CA972632DDB600A54956 /* CommonOSLog in Frameworks */ = {isa = PBXBuildFile; productRef = DB00CA962632DDB600A54956 /* CommonOSLog */; };
|
||||||
DB0140A125C40C0600F9F3CF /* MastodonPinBasedAuthenticationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0140A025C40C0600F9F3CF /* MastodonPinBasedAuthenticationViewController.swift */; };
|
|
||||||
DB0140A825C40C1500F9F3CF /* MastodonPinBasedAuthenticationViewModelNavigationDelegateShim.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0140A725C40C1500F9F3CF /* MastodonPinBasedAuthenticationViewModelNavigationDelegateShim.swift */; };
|
|
||||||
DB0140AE25C40C7300F9F3CF /* MastodonPinBasedAuthenticationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0140AD25C40C7300F9F3CF /* MastodonPinBasedAuthenticationViewModel.swift */; };
|
|
||||||
DB0140BD25C40D7500F9F3CF /* CommonOSLog in Frameworks */ = {isa = PBXBuildFile; productRef = DB0140BC25C40D7500F9F3CF /* CommonOSLog */; };
|
DB0140BD25C40D7500F9F3CF /* CommonOSLog in Frameworks */ = {isa = PBXBuildFile; productRef = DB0140BC25C40D7500F9F3CF /* CommonOSLog */; };
|
||||||
DB0140CF25C42AEE00F9F3CF /* OSLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0140CE25C42AEE00F9F3CF /* OSLog.swift */; };
|
DB0140CF25C42AEE00F9F3CF /* OSLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0140CE25C42AEE00F9F3CF /* OSLog.swift */; };
|
||||||
|
DB029E95266A20430062874E /* MastodonAuthenticationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB029E94266A20430062874E /* MastodonAuthenticationController.swift */; };
|
||||||
DB02CDAB26256A9500D0A2AF /* ThreadReplyLoaderTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB02CDAA26256A9500D0A2AF /* ThreadReplyLoaderTableViewCell.swift */; };
|
DB02CDAB26256A9500D0A2AF /* ThreadReplyLoaderTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB02CDAA26256A9500D0A2AF /* ThreadReplyLoaderTableViewCell.swift */; };
|
||||||
DB02CDBF2625AE5000D0A2AF /* AdaptiveUserInterfaceStyleBarButtonItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB02CDBE2625AE5000D0A2AF /* AdaptiveUserInterfaceStyleBarButtonItem.swift */; };
|
DB02CDBF2625AE5000D0A2AF /* AdaptiveUserInterfaceStyleBarButtonItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB02CDBE2625AE5000D0A2AF /* AdaptiveUserInterfaceStyleBarButtonItem.swift */; };
|
||||||
DB040ECD26526EA600BEE9D8 /* ComposeCollectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB040ECC26526EA600BEE9D8 /* ComposeCollectionView.swift */; };
|
DB040ECD26526EA600BEE9D8 /* ComposeCollectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB040ECC26526EA600BEE9D8 /* ComposeCollectionView.swift */; };
|
||||||
|
@ -753,10 +751,8 @@
|
||||||
BB482D32A7B9825BF5327C4F /* Pods-Mastodon-MastodonUITests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Mastodon-MastodonUITests.release.xcconfig"; path = "Target Support Files/Pods-Mastodon-MastodonUITests/Pods-Mastodon-MastodonUITests.release.xcconfig"; sourceTree = "<group>"; };
|
BB482D32A7B9825BF5327C4F /* Pods-Mastodon-MastodonUITests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Mastodon-MastodonUITests.release.xcconfig"; path = "Target Support Files/Pods-Mastodon-MastodonUITests/Pods-Mastodon-MastodonUITests.release.xcconfig"; sourceTree = "<group>"; };
|
||||||
CD92E0F10BDE4FE7C4B999F2 /* Pods_MastodonTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_MastodonTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
CD92E0F10BDE4FE7C4B999F2 /* Pods_MastodonTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_MastodonTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
D7D7CF93E262178800077512 /* Pods-Mastodon-AppShared.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Mastodon-AppShared.debug.xcconfig"; path = "Target Support Files/Pods-Mastodon-AppShared/Pods-Mastodon-AppShared.debug.xcconfig"; sourceTree = "<group>"; };
|
D7D7CF93E262178800077512 /* Pods-Mastodon-AppShared.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Mastodon-AppShared.debug.xcconfig"; path = "Target Support Files/Pods-Mastodon-AppShared/Pods-Mastodon-AppShared.debug.xcconfig"; sourceTree = "<group>"; };
|
||||||
DB0140A025C40C0600F9F3CF /* MastodonPinBasedAuthenticationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonPinBasedAuthenticationViewController.swift; sourceTree = "<group>"; };
|
|
||||||
DB0140A725C40C1500F9F3CF /* MastodonPinBasedAuthenticationViewModelNavigationDelegateShim.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MastodonPinBasedAuthenticationViewModelNavigationDelegateShim.swift; sourceTree = "<group>"; };
|
|
||||||
DB0140AD25C40C7300F9F3CF /* MastodonPinBasedAuthenticationViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonPinBasedAuthenticationViewModel.swift; sourceTree = "<group>"; };
|
|
||||||
DB0140CE25C42AEE00F9F3CF /* OSLog.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OSLog.swift; sourceTree = "<group>"; };
|
DB0140CE25C42AEE00F9F3CF /* OSLog.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OSLog.swift; sourceTree = "<group>"; };
|
||||||
|
DB029E94266A20430062874E /* MastodonAuthenticationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonAuthenticationController.swift; sourceTree = "<group>"; };
|
||||||
DB02CDAA26256A9500D0A2AF /* ThreadReplyLoaderTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadReplyLoaderTableViewCell.swift; sourceTree = "<group>"; };
|
DB02CDAA26256A9500D0A2AF /* ThreadReplyLoaderTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadReplyLoaderTableViewCell.swift; sourceTree = "<group>"; };
|
||||||
DB02CDBE2625AE5000D0A2AF /* AdaptiveUserInterfaceStyleBarButtonItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdaptiveUserInterfaceStyleBarButtonItem.swift; sourceTree = "<group>"; };
|
DB02CDBE2625AE5000D0A2AF /* AdaptiveUserInterfaceStyleBarButtonItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdaptiveUserInterfaceStyleBarButtonItem.swift; sourceTree = "<group>"; };
|
||||||
DB040ECC26526EA600BEE9D8 /* ComposeCollectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeCollectionView.swift; sourceTree = "<group>"; };
|
DB040ECC26526EA600BEE9D8 /* ComposeCollectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeCollectionView.swift; sourceTree = "<group>"; };
|
||||||
|
@ -1618,7 +1614,6 @@
|
||||||
DB68A03825E900CC00CFDF14 /* Share */,
|
DB68A03825E900CC00CFDF14 /* Share */,
|
||||||
0FAA0FDD25E0B5700017CCDE /* Welcome */,
|
0FAA0FDD25E0B5700017CCDE /* Welcome */,
|
||||||
0FAA102525E1125D0017CCDE /* PickServer */,
|
0FAA102525E1125D0017CCDE /* PickServer */,
|
||||||
DB0140A625C40C0900F9F3CF /* PinBasedAuthentication */,
|
|
||||||
DBE0821A25CD382900FD6BBD /* Register */,
|
DBE0821A25CD382900FD6BBD /* Register */,
|
||||||
DB72602125E36A2500235243 /* ServerRules */,
|
DB72602125E36A2500235243 /* ServerRules */,
|
||||||
2D364F7025E66D5B00204FDC /* ResendEmail */,
|
2D364F7025E66D5B00204FDC /* ResendEmail */,
|
||||||
|
@ -1627,16 +1622,6 @@
|
||||||
path = Onboarding;
|
path = Onboarding;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
DB0140A625C40C0900F9F3CF /* PinBasedAuthentication */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
DB0140A025C40C0600F9F3CF /* MastodonPinBasedAuthenticationViewController.swift */,
|
|
||||||
DB0140AD25C40C7300F9F3CF /* MastodonPinBasedAuthenticationViewModel.swift */,
|
|
||||||
DB0140A725C40C1500F9F3CF /* MastodonPinBasedAuthenticationViewModelNavigationDelegateShim.swift */,
|
|
||||||
);
|
|
||||||
path = PinBasedAuthentication;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
DB084B5125CBC56300F898ED /* CoreDataStack */ = {
|
DB084B5125CBC56300F898ED /* CoreDataStack */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
@ -1921,6 +1906,7 @@
|
||||||
children = (
|
children = (
|
||||||
2D82B9FE25E7863200E36F0F /* OnboardingViewControllerAppearance.swift */,
|
2D82B9FE25E7863200E36F0F /* OnboardingViewControllerAppearance.swift */,
|
||||||
DB2F073325E8ECF000957B2D /* AuthenticationViewModel.swift */,
|
DB2F073325E8ECF000957B2D /* AuthenticationViewModel.swift */,
|
||||||
|
DB029E94266A20430062874E /* MastodonAuthenticationController.swift */,
|
||||||
);
|
);
|
||||||
path = Share;
|
path = Share;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -3043,6 +3029,7 @@
|
||||||
DB49A62B25FF36C700B98345 /* APIService+CustomEmoji.swift in Sources */,
|
DB49A62B25FF36C700B98345 /* APIService+CustomEmoji.swift in Sources */,
|
||||||
DBCBED1D26132E1A00B49291 /* StatusFetchedResultsController.swift in Sources */,
|
DBCBED1D26132E1A00B49291 /* StatusFetchedResultsController.swift in Sources */,
|
||||||
2D79E701261EA5550011E398 /* APIService+CoreData+Tag.swift in Sources */,
|
2D79E701261EA5550011E398 /* APIService+CoreData+Tag.swift in Sources */,
|
||||||
|
DB029E95266A20430062874E /* MastodonAuthenticationController.swift in Sources */,
|
||||||
5B90C461262599800002E742 /* SettingsLinkTableViewCell.swift in Sources */,
|
5B90C461262599800002E742 /* SettingsLinkTableViewCell.swift in Sources */,
|
||||||
DB6180DD263918E30018D199 /* MediaPreviewViewController.swift in Sources */,
|
DB6180DD263918E30018D199 /* MediaPreviewViewController.swift in Sources */,
|
||||||
DBE3CDEC261C6B2900430CC6 /* FavoriteViewController.swift in Sources */,
|
DBE3CDEC261C6B2900430CC6 /* FavoriteViewController.swift in Sources */,
|
||||||
|
@ -3149,7 +3136,6 @@
|
||||||
DBB525562611EDCA002F1F29 /* UserTimelineViewModel.swift in Sources */,
|
DBB525562611EDCA002F1F29 /* UserTimelineViewModel.swift in Sources */,
|
||||||
2D42FF7E25C82218004A627A /* ActionToolBarContainer.swift in Sources */,
|
2D42FF7E25C82218004A627A /* ActionToolBarContainer.swift in Sources */,
|
||||||
DB221B16260C395900AEFE46 /* CustomEmojiPickerInputViewModel.swift in Sources */,
|
DB221B16260C395900AEFE46 /* CustomEmojiPickerInputViewModel.swift in Sources */,
|
||||||
DB0140A125C40C0600F9F3CF /* MastodonPinBasedAuthenticationViewController.swift in Sources */,
|
|
||||||
DB789A1C25F9F76A0071ACA0 /* ComposeStatusContentCollectionViewCell.swift in Sources */,
|
DB789A1C25F9F76A0071ACA0 /* ComposeStatusContentCollectionViewCell.swift in Sources */,
|
||||||
DB1FD43625F26899004CFCFC /* MastodonPickServerViewModel+LoadIndexedServerState.swift in Sources */,
|
DB1FD43625F26899004CFCFC /* MastodonPickServerViewModel+LoadIndexedServerState.swift in Sources */,
|
||||||
2D939AE825EE1CF80076FA61 /* MastodonRegisterViewController+Avatar.swift in Sources */,
|
2D939AE825EE1CF80076FA61 /* MastodonRegisterViewController+Avatar.swift in Sources */,
|
||||||
|
@ -3220,7 +3206,6 @@
|
||||||
2D4AD89C263165B500613EFC /* SuggestionAccountCollectionViewCell.swift in Sources */,
|
2D4AD89C263165B500613EFC /* SuggestionAccountCollectionViewCell.swift in Sources */,
|
||||||
DB447691260B406600B66B82 /* CustomEmojiPickerItemCollectionViewCell.swift in Sources */,
|
DB447691260B406600B66B82 /* CustomEmojiPickerItemCollectionViewCell.swift in Sources */,
|
||||||
DB9282B225F3222800823B15 /* PickServerEmptyStateView.swift in Sources */,
|
DB9282B225F3222800823B15 /* PickServerEmptyStateView.swift in Sources */,
|
||||||
DB0140A825C40C1500F9F3CF /* MastodonPinBasedAuthenticationViewModelNavigationDelegateShim.swift in Sources */,
|
|
||||||
DB1FD45025F26FA1004CFCFC /* MastodonPickServerViewModel+Diffable.swift in Sources */,
|
DB1FD45025F26FA1004CFCFC /* MastodonPickServerViewModel+Diffable.swift in Sources */,
|
||||||
DB2B3AE925E38850007045F9 /* UIViewPreview.swift in Sources */,
|
DB2B3AE925E38850007045F9 /* UIViewPreview.swift in Sources */,
|
||||||
DB68046C2636DC9E00430867 /* MastodonNotification.swift in Sources */,
|
DB68046C2636DC9E00430867 /* MastodonNotification.swift in Sources */,
|
||||||
|
@ -3280,7 +3265,6 @@
|
||||||
DB8190C62601FF0400020C08 /* AttachmentContainerView.swift in Sources */,
|
DB8190C62601FF0400020C08 /* AttachmentContainerView.swift in Sources */,
|
||||||
DB51D173262832380062B7A1 /* BlurHashEncode.swift in Sources */,
|
DB51D173262832380062B7A1 /* BlurHashEncode.swift in Sources */,
|
||||||
DB5086AB25CC0BBB00C2C187 /* AvatarConfigurableView.swift in Sources */,
|
DB5086AB25CC0BBB00C2C187 /* AvatarConfigurableView.swift in Sources */,
|
||||||
DB0140AE25C40C7300F9F3CF /* MastodonPinBasedAuthenticationViewModel.swift in Sources */,
|
|
||||||
2D32EAAC25CB96DC00C9ED86 /* TimelineMiddleLoaderTableViewCell.swift in Sources */,
|
2D32EAAC25CB96DC00C9ED86 /* TimelineMiddleLoaderTableViewCell.swift in Sources */,
|
||||||
DB71FD2C25F86A5100512AE1 /* AvatarStackContainerButton.swift in Sources */,
|
DB71FD2C25F86A5100512AE1 /* AvatarStackContainerButton.swift in Sources */,
|
||||||
DB87D4512609CF1E00D12C0D /* ComposeStatusPollOptionAppendEntryCollectionViewCell.swift in Sources */,
|
DB87D4512609CF1E00D12C0D /* ComposeStatusPollOptionAppendEntryCollectionViewCell.swift in Sources */,
|
||||||
|
@ -3630,16 +3614,16 @@
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_ENTITLEMENTS = Mastodon/Mastodon.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Mastodon/Mastodon.entitlements;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 5;
|
CURRENT_PROJECT_VERSION = 10;
|
||||||
DEVELOPMENT_ASSET_PATHS = "Mastodon/Resources/Preview\\ Assets.xcassets";
|
DEVELOPMENT_ASSET_PATHS = "Mastodon/Resources/Preview\\ Assets.xcassets";
|
||||||
DEVELOPMENT_TEAM = 7LFDZ96332;
|
DEVELOPMENT_TEAM = 5Z4GVSS33P;
|
||||||
INFOPLIST_FILE = Mastodon/Info.plist;
|
INFOPLIST_FILE = Mastodon/Info.plist;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 0.5.0;
|
MARKETING_VERSION = 0.6.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.Mastodon;
|
PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
|
@ -3657,16 +3641,16 @@
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_ENTITLEMENTS = Mastodon/Mastodon.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Mastodon/Mastodon.entitlements;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 5;
|
CURRENT_PROJECT_VERSION = 10;
|
||||||
DEVELOPMENT_ASSET_PATHS = "Mastodon/Resources/Preview\\ Assets.xcassets";
|
DEVELOPMENT_ASSET_PATHS = "Mastodon/Resources/Preview\\ Assets.xcassets";
|
||||||
DEVELOPMENT_TEAM = 7LFDZ96332;
|
DEVELOPMENT_TEAM = 5Z4GVSS33P;
|
||||||
INFOPLIST_FILE = Mastodon/Info.plist;
|
INFOPLIST_FILE = Mastodon/Info.plist;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 0.5.0;
|
MARKETING_VERSION = 0.6.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.Mastodon;
|
PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
|
@ -3680,7 +3664,7 @@
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
DEVELOPMENT_TEAM = 7LFDZ96332;
|
DEVELOPMENT_TEAM = 5Z4GVSS33P;
|
||||||
INFOPLIST_FILE = MastodonTests/Info.plist;
|
INFOPLIST_FILE = MastodonTests/Info.plist;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
|
@ -3701,7 +3685,7 @@
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
DEVELOPMENT_TEAM = 7LFDZ96332;
|
DEVELOPMENT_TEAM = 5Z4GVSS33P;
|
||||||
INFOPLIST_FILE = MastodonTests/Info.plist;
|
INFOPLIST_FILE = MastodonTests/Info.plist;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
|
@ -3721,7 +3705,7 @@
|
||||||
baseConfigurationReference = 459EA4F43058CAB47719E963 /* Pods-Mastodon-MastodonUITests.debug.xcconfig */;
|
baseConfigurationReference = 459EA4F43058CAB47719E963 /* Pods-Mastodon-MastodonUITests.debug.xcconfig */;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
DEVELOPMENT_TEAM = 7LFDZ96332;
|
DEVELOPMENT_TEAM = 5Z4GVSS33P;
|
||||||
INFOPLIST_FILE = MastodonUITests/Info.plist;
|
INFOPLIST_FILE = MastodonUITests/Info.plist;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
|
@ -3741,7 +3725,7 @@
|
||||||
baseConfigurationReference = BB482D32A7B9825BF5327C4F /* Pods-Mastodon-MastodonUITests.release.xcconfig */;
|
baseConfigurationReference = BB482D32A7B9825BF5327C4F /* Pods-Mastodon-MastodonUITests.release.xcconfig */;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
DEVELOPMENT_TEAM = 7LFDZ96332;
|
DEVELOPMENT_TEAM = 5Z4GVSS33P;
|
||||||
INFOPLIST_FILE = MastodonUITests/Info.plist;
|
INFOPLIST_FILE = MastodonUITests/Info.plist;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
|
@ -3765,7 +3749,7 @@
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
DEFINES_MODULE = YES;
|
DEFINES_MODULE = YES;
|
||||||
DEVELOPMENT_TEAM = 7LFDZ96332;
|
DEVELOPMENT_TEAM = 5Z4GVSS33P;
|
||||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||||
DYLIB_CURRENT_VERSION = 1;
|
DYLIB_CURRENT_VERSION = 1;
|
||||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||||
|
@ -3776,7 +3760,7 @@
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
"@loader_path/Frameworks",
|
"@loader_path/Frameworks",
|
||||||
);
|
);
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.Mastodon.AppShared;
|
PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app.AppShared;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||||
SKIP_INSTALL = YES;
|
SKIP_INSTALL = YES;
|
||||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
|
@ -3796,7 +3780,7 @@
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
DEFINES_MODULE = YES;
|
DEFINES_MODULE = YES;
|
||||||
DEVELOPMENT_TEAM = 7LFDZ96332;
|
DEVELOPMENT_TEAM = 5Z4GVSS33P;
|
||||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||||
DYLIB_CURRENT_VERSION = 1;
|
DYLIB_CURRENT_VERSION = 1;
|
||||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||||
|
@ -3807,7 +3791,7 @@
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
"@loader_path/Frameworks",
|
"@loader_path/Frameworks",
|
||||||
);
|
);
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.Mastodon.AppShared;
|
PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app.AppShared;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||||
SKIP_INSTALL = YES;
|
SKIP_INSTALL = YES;
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
|
@ -3824,7 +3808,7 @@
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
DEFINES_MODULE = YES;
|
DEFINES_MODULE = YES;
|
||||||
DEVELOPMENT_TEAM = 7LFDZ96332;
|
DEVELOPMENT_TEAM = 5Z4GVSS33P;
|
||||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||||
DYLIB_CURRENT_VERSION = 1;
|
DYLIB_CURRENT_VERSION = 1;
|
||||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||||
|
@ -3853,7 +3837,7 @@
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
DEFINES_MODULE = YES;
|
DEFINES_MODULE = YES;
|
||||||
DEVELOPMENT_TEAM = 7LFDZ96332;
|
DEVELOPMENT_TEAM = 5Z4GVSS33P;
|
||||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||||
DYLIB_CURRENT_VERSION = 1;
|
DYLIB_CURRENT_VERSION = 1;
|
||||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||||
|
@ -3879,7 +3863,7 @@
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
DEVELOPMENT_TEAM = 7LFDZ96332;
|
DEVELOPMENT_TEAM = 5Z4GVSS33P;
|
||||||
INFOPLIST_FILE = CoreDataStackTests/Info.plist;
|
INFOPLIST_FILE = CoreDataStackTests/Info.plist;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
|
@ -3899,7 +3883,7 @@
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
DEVELOPMENT_TEAM = 7LFDZ96332;
|
DEVELOPMENT_TEAM = 5Z4GVSS33P;
|
||||||
INFOPLIST_FILE = CoreDataStackTests/Info.plist;
|
INFOPLIST_FILE = CoreDataStackTests/Info.plist;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
|
@ -3920,16 +3904,16 @@
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
CODE_SIGN_ENTITLEMENTS = NotificationService/NotificationService.entitlements;
|
CODE_SIGN_ENTITLEMENTS = NotificationService/NotificationService.entitlements;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 5;
|
CURRENT_PROJECT_VERSION = 10;
|
||||||
DEVELOPMENT_TEAM = 7LFDZ96332;
|
DEVELOPMENT_TEAM = 5Z4GVSS33P;
|
||||||
INFOPLIST_FILE = NotificationService/Info.plist;
|
INFOPLIST_FILE = NotificationService/Info.plist;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
"@executable_path/../../Frameworks",
|
"@executable_path/../../Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 0.5.0;
|
MARKETING_VERSION = 0.6.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.Mastodon.NotificationService;
|
PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app.NotificationService;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SKIP_INSTALL = YES;
|
SKIP_INSTALL = YES;
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
|
@ -3943,16 +3927,16 @@
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
CODE_SIGN_ENTITLEMENTS = NotificationService/NotificationService.entitlements;
|
CODE_SIGN_ENTITLEMENTS = NotificationService/NotificationService.entitlements;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 5;
|
CURRENT_PROJECT_VERSION = 10;
|
||||||
DEVELOPMENT_TEAM = 7LFDZ96332;
|
DEVELOPMENT_TEAM = 5Z4GVSS33P;
|
||||||
INFOPLIST_FILE = NotificationService/Info.plist;
|
INFOPLIST_FILE = NotificationService/Info.plist;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
"@executable_path/../../Frameworks",
|
"@executable_path/../../Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 0.5.0;
|
MARKETING_VERSION = 0.6.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.Mastodon.NotificationService;
|
PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app.NotificationService;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SKIP_INSTALL = YES;
|
SKIP_INSTALL = YES;
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
<key>AppShared.xcscheme_^#shared#^_</key>
|
<key>AppShared.xcscheme_^#shared#^_</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>orderHint</key>
|
<key>orderHint</key>
|
||||||
<integer>18</integer>
|
<integer>14</integer>
|
||||||
</dict>
|
</dict>
|
||||||
<key>CoreDataStack.xcscheme_^#shared#^_</key>
|
<key>CoreDataStack.xcscheme_^#shared#^_</key>
|
||||||
<dict>
|
<dict>
|
||||||
|
@ -27,12 +27,12 @@
|
||||||
<key>Mastodon.xcscheme_^#shared#^_</key>
|
<key>Mastodon.xcscheme_^#shared#^_</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>orderHint</key>
|
<key>orderHint</key>
|
||||||
<integer>1</integer>
|
<integer>13</integer>
|
||||||
</dict>
|
</dict>
|
||||||
<key>NotificationService.xcscheme_^#shared#^_</key>
|
<key>NotificationService.xcscheme_^#shared#^_</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>orderHint</key>
|
<key>orderHint</key>
|
||||||
<integer>16</integer>
|
<integer>12</integer>
|
||||||
</dict>
|
</dict>
|
||||||
</dict>
|
</dict>
|
||||||
<key>SuppressBuildableAutocreation</key>
|
<key>SuppressBuildableAutocreation</key>
|
||||||
|
|
|
@ -18,7 +18,7 @@ final class SafariActivity: UIActivity {
|
||||||
}
|
}
|
||||||
|
|
||||||
override var activityType: UIActivity.ActivityType? {
|
override var activityType: UIActivity.ActivityType? {
|
||||||
return UIActivity.ActivityType("org.joinmastodon.Mastodon.safari-activity")
|
return UIActivity.ActivityType("org.joinmastodon.app.safari-activity")
|
||||||
}
|
}
|
||||||
|
|
||||||
override var activityTitle: String? {
|
override var activityTitle: String? {
|
||||||
|
|
|
@ -42,7 +42,6 @@ extension SceneCoordinator {
|
||||||
// onboarding
|
// onboarding
|
||||||
case welcome
|
case welcome
|
||||||
case mastodonPickServer(viewMode: MastodonPickServerViewModel)
|
case mastodonPickServer(viewMode: MastodonPickServerViewModel)
|
||||||
case mastodonPinBasedAuthentication(viewModel: MastodonPinBasedAuthenticationViewModel)
|
|
||||||
case mastodonRegister(viewModel: MastodonRegisterViewModel)
|
case mastodonRegister(viewModel: MastodonRegisterViewModel)
|
||||||
case mastodonServerRules(viewModel: MastodonServerRulesViewModel)
|
case mastodonServerRules(viewModel: MastodonServerRulesViewModel)
|
||||||
case mastodonConfirmEmail(viewModel: MastodonConfirmEmailViewModel)
|
case mastodonConfirmEmail(viewModel: MastodonConfirmEmailViewModel)
|
||||||
|
@ -78,6 +77,7 @@ extension SceneCoordinator {
|
||||||
case safari(url: URL)
|
case safari(url: URL)
|
||||||
case alertController(alertController: UIAlertController)
|
case alertController(alertController: UIAlertController)
|
||||||
case activityViewController(activityViewController: UIActivityViewController, sourceView: UIView?, barButtonItem: UIBarButtonItem?)
|
case activityViewController(activityViewController: UIActivityViewController, sourceView: UIView?, barButtonItem: UIBarButtonItem?)
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
case publicTimeline
|
case publicTimeline
|
||||||
#endif
|
#endif
|
||||||
|
@ -86,7 +86,6 @@ extension SceneCoordinator {
|
||||||
switch self {
|
switch self {
|
||||||
case .welcome,
|
case .welcome,
|
||||||
.mastodonPickServer,
|
.mastodonPickServer,
|
||||||
.mastodonPinBasedAuthentication,
|
|
||||||
.mastodonRegister,
|
.mastodonRegister,
|
||||||
.mastodonServerRules,
|
.mastodonServerRules,
|
||||||
.mastodonConfirmEmail,
|
.mastodonConfirmEmail,
|
||||||
|
@ -217,10 +216,6 @@ private extension SceneCoordinator {
|
||||||
let _viewController = MastodonPickServerViewController()
|
let _viewController = MastodonPickServerViewController()
|
||||||
_viewController.viewModel = viewModel
|
_viewController.viewModel = viewModel
|
||||||
viewController = _viewController
|
viewController = _viewController
|
||||||
case .mastodonPinBasedAuthentication(let viewModel):
|
|
||||||
let _viewController = MastodonPinBasedAuthenticationViewController()
|
|
||||||
_viewController.viewModel = viewModel
|
|
||||||
viewController = _viewController
|
|
||||||
case .mastodonRegister(let viewModel):
|
case .mastodonRegister(let viewModel):
|
||||||
let _viewController = MastodonRegisterViewController()
|
let _viewController = MastodonRegisterViewController()
|
||||||
_viewController.viewModel = viewModel
|
_viewController.viewModel = viewModel
|
||||||
|
|
|
@ -141,8 +141,8 @@ extension ComposeStatusSection {
|
||||||
attribute.contentWarningContent.value = text
|
attribute.contentWarningContent.value = text
|
||||||
}
|
}
|
||||||
.store(in: &cell.disposeBag)
|
.store(in: &cell.disposeBag)
|
||||||
ComposeStatusSection.configureCustomEmojiPicker(viewModel: customEmojiPickerInputViewModel, customEmojiReplacableTextInput: cell.textEditorView, disposeBag: &cell.disposeBag)
|
ComposeStatusSection.configureCustomEmojiPicker(viewModel: customEmojiPickerInputViewModel, customEmojiReplaceableTextInput: cell.textEditorView, disposeBag: &cell.disposeBag)
|
||||||
ComposeStatusSection.configureCustomEmojiPicker(viewModel: customEmojiPickerInputViewModel, customEmojiReplacableTextInput: cell.statusContentWarningEditorView.textView, disposeBag: &cell.disposeBag)
|
ComposeStatusSection.configureCustomEmojiPicker(viewModel: customEmojiPickerInputViewModel, customEmojiReplaceableTextInput: cell.statusContentWarningEditorView.textView, disposeBag: &cell.disposeBag)
|
||||||
|
|
||||||
return cell
|
return cell
|
||||||
case .attachment(let attachmentService):
|
case .attachment(let attachmentService):
|
||||||
|
@ -228,7 +228,7 @@ extension ComposeStatusSection {
|
||||||
.assign(to: \.value, on: attribute.option)
|
.assign(to: \.value, on: attribute.option)
|
||||||
.store(in: &cell.disposeBag)
|
.store(in: &cell.disposeBag)
|
||||||
cell.delegate = composeStatusPollOptionCollectionViewCellDelegate
|
cell.delegate = composeStatusPollOptionCollectionViewCellDelegate
|
||||||
ComposeStatusSection.configureCustomEmojiPicker(viewModel: customEmojiPickerInputViewModel, customEmojiReplacableTextInput: cell.pollOptionView.optionTextField, disposeBag: &cell.disposeBag)
|
ComposeStatusSection.configureCustomEmojiPicker(viewModel: customEmojiPickerInputViewModel, customEmojiReplaceableTextInput: cell.pollOptionView.optionTextField, disposeBag: &cell.disposeBag)
|
||||||
return cell
|
return cell
|
||||||
case .pollOptionAppendEntry:
|
case .pollOptionAppendEntry:
|
||||||
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: String(describing: ComposeStatusPollOptionAppendEntryCollectionViewCell.self), for: indexPath) as! ComposeStatusPollOptionAppendEntryCollectionViewCell
|
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: String(describing: ComposeStatusPollOptionAppendEntryCollectionViewCell.self), for: indexPath) as! ComposeStatusPollOptionAppendEntryCollectionViewCell
|
||||||
|
@ -295,7 +295,7 @@ protocol CustomEmojiReplaceableTextInput: AnyObject {
|
||||||
var isFirstResponder: Bool { get }
|
var isFirstResponder: Bool { get }
|
||||||
}
|
}
|
||||||
|
|
||||||
class CustomEmojiReplacableTextInputReference {
|
class CustomEmojiReplaceableTextInputReference {
|
||||||
weak var value: CustomEmojiReplaceableTextInput?
|
weak var value: CustomEmojiReplaceableTextInput?
|
||||||
|
|
||||||
init(value: CustomEmojiReplaceableTextInput? = nil) {
|
init(value: CustomEmojiReplaceableTextInput? = nil) {
|
||||||
|
@ -320,7 +320,7 @@ extension ComposeStatusSection {
|
||||||
|
|
||||||
static func configureCustomEmojiPicker(
|
static func configureCustomEmojiPicker(
|
||||||
viewModel: CustomEmojiPickerInputViewModel?,
|
viewModel: CustomEmojiPickerInputViewModel?,
|
||||||
customEmojiReplacableTextInput: CustomEmojiReplaceableTextInput,
|
customEmojiReplaceableTextInput: CustomEmojiReplaceableTextInput,
|
||||||
disposeBag: inout Set<AnyCancellable>
|
disposeBag: inout Set<AnyCancellable>
|
||||||
) {
|
) {
|
||||||
guard let viewModel = viewModel else { return }
|
guard let viewModel = viewModel else { return }
|
||||||
|
@ -328,9 +328,9 @@ extension ComposeStatusSection {
|
||||||
.receive(on: DispatchQueue.main)
|
.receive(on: DispatchQueue.main)
|
||||||
.sink { [weak viewModel] isCustomEmojiComposing in
|
.sink { [weak viewModel] isCustomEmojiComposing in
|
||||||
guard let viewModel = viewModel else { return }
|
guard let viewModel = viewModel else { return }
|
||||||
customEmojiReplacableTextInput.inputView = isCustomEmojiComposing ? viewModel.customEmojiPickerInputView : nil
|
customEmojiReplaceableTextInput.inputView = isCustomEmojiComposing ? viewModel.customEmojiPickerInputView : nil
|
||||||
customEmojiReplacableTextInput.reloadInputViews()
|
customEmojiReplaceableTextInput.reloadInputViews()
|
||||||
viewModel.append(customEmojiReplacableTextInput: customEmojiReplacableTextInput)
|
viewModel.append(customEmojiReplaceableTextInput: customEmojiReplaceableTextInput)
|
||||||
}
|
}
|
||||||
.store(in: &disposeBag)
|
.store(in: &disposeBag)
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
<string>development</string>
|
<string>development</string>
|
||||||
<key>com.apple.security.application-groups</key>
|
<key>com.apple.security.application-groups</key>
|
||||||
<array>
|
<array>
|
||||||
<string>group.org.joinmastodon.mastodon-temp</string>
|
<string>group.org.joinmastodon.app</string>
|
||||||
</array>
|
</array>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|
|
@ -61,7 +61,8 @@ final class ComposeViewController: UIViewController, NeedsDependency {
|
||||||
var systemKeyboardHeight: CGFloat = .zero {
|
var systemKeyboardHeight: CGFloat = .zero {
|
||||||
didSet {
|
didSet {
|
||||||
// note: some system AutoLayout warning here
|
// note: some system AutoLayout warning here
|
||||||
customEmojiPickerInputView.frame.size.height = systemKeyboardHeight != .zero ? systemKeyboardHeight : 300
|
let height = max(300, systemKeyboardHeight)
|
||||||
|
customEmojiPickerInputView.frame.size.height = height
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -730,7 +731,7 @@ extension ComposeViewController: TextEditorViewTextAttributesDelegate {
|
||||||
guard let emoji = customEmojiViewModel.emoji(shortcode: name) else { continue }
|
guard let emoji = customEmojiViewModel.emoji(shortcode: name) else { continue }
|
||||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: handle emoji: %s", ((#file as NSString).lastPathComponent), #line, #function, name)
|
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: handle emoji: %s", ((#file as NSString).lastPathComponent), #line, #function, name)
|
||||||
|
|
||||||
// set emoji token invisiable (without upper bounce space)
|
// set emoji token invisible (without upper bounce space)
|
||||||
var attributes = [NSAttributedString.Key: Any]()
|
var attributes = [NSAttributedString.Key: Any]()
|
||||||
attributes[.font] = UIFont.systemFont(ofSize: 0.01)
|
attributes[.font] = UIFont.systemFont(ofSize: 0.01)
|
||||||
attributedString.addAttributes(attributes, range: match.range)
|
attributedString.addAttributes(attributes, range: match.range)
|
||||||
|
@ -812,15 +813,15 @@ extension ComposeViewController: TextEditorViewTextAttributesDelegate {
|
||||||
extension ComposeViewController: TextEditorViewChangeObserver {
|
extension ComposeViewController: TextEditorViewChangeObserver {
|
||||||
|
|
||||||
func textEditorView(_ textEditorView: TextEditorView, didChangeWithChangeResult changeResult: TextEditorViewChangeResult) {
|
func textEditorView(_ textEditorView: TextEditorView, didChangeWithChangeResult changeResult: TextEditorViewChangeResult) {
|
||||||
guard var autoCompeletion = ComposeViewController.scanAutoCompleteInfo(textEditorView: textEditorView) else {
|
guard var autoCompletion = ComposeViewController.scanAutoCompleteInfo(textEditorView: textEditorView) else {
|
||||||
viewModel.autoCompleteInfo.value = nil
|
viewModel.autoCompleteInfo.value = nil
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: auto complete %s (%s)", ((#file as NSString).lastPathComponent), #line, #function, String(autoCompeletion.toHighlightEndString), String(autoCompeletion.toCursorString))
|
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: auto complete %s (%s)", ((#file as NSString).lastPathComponent), #line, #function, String(autoCompletion.toHighlightEndString), String(autoCompletion.toCursorString))
|
||||||
|
|
||||||
// get layout text bounding rect
|
// get layout text bounding rect
|
||||||
var glyphRange = NSRange()
|
var glyphRange = NSRange()
|
||||||
textEditorView.layoutManager.characterRange(forGlyphRange: NSRange(autoCompeletion.toCursorRange, in: textEditorView.text), actualGlyphRange: &glyphRange)
|
textEditorView.layoutManager.characterRange(forGlyphRange: NSRange(autoCompletion.toCursorRange, in: textEditorView.text), actualGlyphRange: &glyphRange)
|
||||||
let textContainer = textEditorView.layoutManager.textContainers[0]
|
let textContainer = textEditorView.layoutManager.textContainers[0]
|
||||||
let textBoundingRect = textEditorView.layoutManager.boundingRect(forGlyphRange: glyphRange, in: textContainer)
|
let textBoundingRect = textEditorView.layoutManager.boundingRect(forGlyphRange: glyphRange, in: textContainer)
|
||||||
|
|
||||||
|
@ -838,13 +839,13 @@ extension ComposeViewController: TextEditorViewChangeObserver {
|
||||||
viewModel.autoCompleteRetryLayoutTimes.value = 0
|
viewModel.autoCompleteRetryLayoutTimes.value = 0
|
||||||
|
|
||||||
// get symbol bounding rect
|
// get symbol bounding rect
|
||||||
textEditorView.layoutManager.characterRange(forGlyphRange: NSRange(autoCompeletion.symbolRange, in: textEditorView.text), actualGlyphRange: &glyphRange)
|
textEditorView.layoutManager.characterRange(forGlyphRange: NSRange(autoCompletion.symbolRange, in: textEditorView.text), actualGlyphRange: &glyphRange)
|
||||||
let symbolBoundingRect = textEditorView.layoutManager.boundingRect(forGlyphRange: glyphRange, in: textContainer)
|
let symbolBoundingRect = textEditorView.layoutManager.boundingRect(forGlyphRange: glyphRange, in: textContainer)
|
||||||
|
|
||||||
// set bounding rect and trigger layout
|
// set bounding rect and trigger layout
|
||||||
autoCompeletion.textBoundingRect = textBoundingRect
|
autoCompletion.textBoundingRect = textBoundingRect
|
||||||
autoCompeletion.symbolBoundingRect = symbolBoundingRect
|
autoCompletion.symbolBoundingRect = symbolBoundingRect
|
||||||
viewModel.autoCompleteInfo.value = autoCompeletion
|
viewModel.autoCompleteInfo.value = autoCompletion
|
||||||
}
|
}
|
||||||
|
|
||||||
struct AutoCompleteInfo {
|
struct AutoCompleteInfo {
|
||||||
|
@ -864,12 +865,14 @@ extension ComposeViewController: TextEditorViewChangeObserver {
|
||||||
|
|
||||||
private static func scanAutoCompleteInfo(textEditorView: TextEditorView) -> AutoCompleteInfo? {
|
private static func scanAutoCompleteInfo(textEditorView: TextEditorView) -> AutoCompleteInfo? {
|
||||||
let text = textEditorView.text
|
let text = textEditorView.text
|
||||||
let cursorLocation = textEditorView.selectedRange.location
|
|
||||||
let cursorIndex = text.index(text.startIndex, offsetBy: cursorLocation)
|
|
||||||
guard cursorLocation > 0, !text.isEmpty else { return nil }
|
|
||||||
|
|
||||||
let _highlighStartIndex: String.Index? = {
|
guard textEditorView.selectedRange.location > 0, !text.isEmpty,
|
||||||
var index = text.index(text.startIndex, offsetBy: cursorLocation - 1)
|
let selectedRange = Range(textEditorView.selectedRange, in: text) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
let cursorIndex = selectedRange.upperBound
|
||||||
|
let _highlightStartIndex: String.Index? = {
|
||||||
|
var index = text.index(before: cursorIndex)
|
||||||
while index > text.startIndex {
|
while index > text.startIndex {
|
||||||
let char = text[index]
|
let char = text[index]
|
||||||
if char == "@" || char == "#" || char == ":" {
|
if char == "@" || char == "#" || char == ":" {
|
||||||
|
@ -886,18 +889,18 @@ extension ComposeViewController: TextEditorViewChangeObserver {
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
guard let highlighStartIndex = _highlighStartIndex else { return nil }
|
guard let highlightStartIndex = _highlightStartIndex else { return nil }
|
||||||
let scanRange = NSRange(highlighStartIndex..<text.endIndex, in: text)
|
let scanRange = NSRange(highlightStartIndex..<text.endIndex, in: text)
|
||||||
|
|
||||||
guard let match = text.firstMatch(pattern: MastodonRegex.autoCompletePattern, options: [], range: scanRange) else { return nil }
|
guard let match = text.firstMatch(pattern: MastodonRegex.autoCompletePattern, options: [], range: scanRange) else { return nil }
|
||||||
let matchRange = match.range(at: 0)
|
guard let matchRange = Range(match.range(at: 0), in: text) else { return nil }
|
||||||
let matchStartIndex = text.index(text.startIndex, offsetBy: matchRange.location)
|
let matchStartIndex = matchRange.lowerBound
|
||||||
let matchEndIndex = text.index(matchStartIndex, offsetBy: matchRange.length)
|
let matchEndIndex = matchRange.upperBound
|
||||||
|
|
||||||
guard matchStartIndex == highlighStartIndex, matchEndIndex >= cursorIndex else { return nil }
|
guard matchStartIndex == highlightStartIndex, matchEndIndex >= cursorIndex else { return nil }
|
||||||
let symbolRange = highlighStartIndex..<text.index(after: highlighStartIndex)
|
let symbolRange = highlightStartIndex..<text.index(after: highlightStartIndex)
|
||||||
let symbolString = text[symbolRange]
|
let symbolString = text[symbolRange]
|
||||||
let toCursorRange = highlighStartIndex..<cursorIndex
|
let toCursorRange = highlightStartIndex..<cursorIndex
|
||||||
let toCursorString = text[toCursorRange]
|
let toCursorString = text[toCursorRange]
|
||||||
let toHighlightEndRange = matchStartIndex..<matchEndIndex
|
let toHighlightEndRange = matchStartIndex..<matchEndIndex
|
||||||
let toHighlightEndString = text[toHighlightEndRange]
|
let toHighlightEndString = text[toHighlightEndRange]
|
||||||
|
@ -1015,7 +1018,7 @@ extension ComposeViewController: UICollectionViewDelegate {
|
||||||
let emoji = attribute.emoji
|
let emoji = attribute.emoji
|
||||||
let textEditorView = self.textEditorView()
|
let textEditorView = self.textEditorView()
|
||||||
|
|
||||||
// retrive active text input and insert emoji
|
// retrieve active text input and insert emoji
|
||||||
// the leading and trailing space is REQUIRED to fix `UITextStorage` layout issue
|
// the leading and trailing space is REQUIRED to fix `UITextStorage` layout issue
|
||||||
let reference = viewModel.customEmojiPickerInputViewModel.insertText(" :\(emoji.shortcode): ")
|
let reference = viewModel.customEmojiPickerInputViewModel.insertText(" :\(emoji.shortcode): ")
|
||||||
|
|
||||||
|
@ -1028,6 +1031,9 @@ extension ComposeViewController: UICollectionViewDelegate {
|
||||||
DispatchQueue.main.async { [weak self] in
|
DispatchQueue.main.async { [weak self] in
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
self.collectionView.collectionViewLayout.invalidateLayout()
|
self.collectionView.collectionViewLayout.invalidateLayout()
|
||||||
|
|
||||||
|
// make click sound
|
||||||
|
UIDevice.current.playInputClick()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -1068,7 +1074,7 @@ extension ComposeViewController: PHPickerViewControllerDelegate {
|
||||||
let service = MastodonAttachmentService(
|
let service = MastodonAttachmentService(
|
||||||
context: context,
|
context: context,
|
||||||
pickerResult: result,
|
pickerResult: result,
|
||||||
initalAuthenticationBox: viewModel.activeAuthenticationBox.value
|
initialAuthenticationBox: viewModel.activeAuthenticationBox.value
|
||||||
)
|
)
|
||||||
return service
|
return service
|
||||||
}
|
}
|
||||||
|
@ -1087,7 +1093,7 @@ extension ComposeViewController: UIImagePickerControllerDelegate & UINavigationC
|
||||||
let attachmentService = MastodonAttachmentService(
|
let attachmentService = MastodonAttachmentService(
|
||||||
context: context,
|
context: context,
|
||||||
image: image,
|
image: image,
|
||||||
initalAuthenticationBox: viewModel.activeAuthenticationBox.value
|
initialAuthenticationBox: viewModel.activeAuthenticationBox.value
|
||||||
)
|
)
|
||||||
viewModel.attachmentServices.value = viewModel.attachmentServices.value + [attachmentService]
|
viewModel.attachmentServices.value = viewModel.attachmentServices.value + [attachmentService]
|
||||||
}
|
}
|
||||||
|
@ -1106,7 +1112,7 @@ extension ComposeViewController: UIDocumentPickerDelegate {
|
||||||
let attachmentService = MastodonAttachmentService(
|
let attachmentService = MastodonAttachmentService(
|
||||||
context: context,
|
context: context,
|
||||||
documentURL: url,
|
documentURL: url,
|
||||||
initalAuthenticationBox: viewModel.activeAuthenticationBox.value
|
initialAuthenticationBox: viewModel.activeAuthenticationBox.value
|
||||||
)
|
)
|
||||||
viewModel.attachmentServices.value = viewModel.attachmentServices.value + [attachmentService]
|
viewModel.attachmentServices.value = viewModel.attachmentServices.value + [attachmentService]
|
||||||
}
|
}
|
||||||
|
|
|
@ -218,6 +218,12 @@ extension ComposeToolbarView {
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updateToolbarButtonUserInterfaceStyle() {
|
private func updateToolbarButtonUserInterfaceStyle() {
|
||||||
|
// reset emoji
|
||||||
|
let emojiButtonImage = Asset.Human.faceSmilingAdaptive.image
|
||||||
|
.af.imageScaled(to: CGSize(width: 20, height: 20))
|
||||||
|
.withRenderingMode(.alwaysTemplate)
|
||||||
|
emojiButton.setImage(emojiButtonImage, for: .normal)
|
||||||
|
|
||||||
switch traitCollection.userInterfaceStyle {
|
switch traitCollection.userInterfaceStyle {
|
||||||
case .light:
|
case .light:
|
||||||
mediaButton.setImage(UIImage(systemName: "photo", withConfiguration: UIImage.SymbolConfiguration(pointSize: 20, weight: .regular))!, for: .normal)
|
mediaButton.setImage(UIImage(systemName: "photo", withConfiguration: UIImage.SymbolConfiguration(pointSize: 20, weight: .regular))!, for: .normal)
|
||||||
|
|
|
@ -84,3 +84,9 @@ extension CustomEmojiPickerInputView {
|
||||||
return layout
|
return layout
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension CustomEmojiPickerInputView: UIInputViewAudioFeedback {
|
||||||
|
var enableInputClicksWhenVisible: Bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@ final class CustomEmojiPickerInputViewModel {
|
||||||
|
|
||||||
var disposeBag = Set<AnyCancellable>()
|
var disposeBag = Set<AnyCancellable>()
|
||||||
|
|
||||||
private var customEmojiReplacableTextInputReferences: [CustomEmojiReplacableTextInputReference] = []
|
private var customEmojiReplaceableTextInputReferences: [CustomEmojiReplaceableTextInputReference] = []
|
||||||
|
|
||||||
// input
|
// input
|
||||||
weak var customEmojiPickerInputView: CustomEmojiPickerInputView?
|
weak var customEmojiPickerInputView: CustomEmojiPickerInputView?
|
||||||
|
@ -25,27 +25,27 @@ final class CustomEmojiPickerInputViewModel {
|
||||||
extension CustomEmojiPickerInputViewModel {
|
extension CustomEmojiPickerInputViewModel {
|
||||||
|
|
||||||
private func removeEmptyReferences() {
|
private func removeEmptyReferences() {
|
||||||
customEmojiReplacableTextInputReferences.removeAll(where: { element in
|
customEmojiReplaceableTextInputReferences.removeAll(where: { element in
|
||||||
element.value == nil
|
element.value == nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func append(customEmojiReplacableTextInput textInput: CustomEmojiReplaceableTextInput) {
|
func append(customEmojiReplaceableTextInput textInput: CustomEmojiReplaceableTextInput) {
|
||||||
removeEmptyReferences()
|
removeEmptyReferences()
|
||||||
|
|
||||||
let isContains = customEmojiReplacableTextInputReferences.contains(where: { element in
|
let isContains = customEmojiReplaceableTextInputReferences.contains(where: { element in
|
||||||
element.value === textInput
|
element.value === textInput
|
||||||
})
|
})
|
||||||
guard !isContains else {
|
guard !isContains else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
customEmojiReplacableTextInputReferences.append(CustomEmojiReplacableTextInputReference(value: textInput))
|
customEmojiReplaceableTextInputReferences.append(CustomEmojiReplaceableTextInputReference(value: textInput))
|
||||||
}
|
}
|
||||||
|
|
||||||
func insertText(_ text: String) -> CustomEmojiReplacableTextInputReference? {
|
func insertText(_ text: String) -> CustomEmojiReplaceableTextInputReference? {
|
||||||
removeEmptyReferences()
|
removeEmptyReferences()
|
||||||
|
|
||||||
for reference in customEmojiReplacableTextInputReferences {
|
for reference in customEmojiReplaceableTextInputReferences {
|
||||||
guard reference.value?.isFirstResponder == true else { continue }
|
guard reference.value?.isFirstResponder == true else { continue }
|
||||||
reference.value?.insertText(text)
|
reference.value?.insertText(text)
|
||||||
return reference
|
return reference
|
||||||
|
|
|
@ -9,6 +9,7 @@ import os.log
|
||||||
import UIKit
|
import UIKit
|
||||||
import Combine
|
import Combine
|
||||||
import GameController
|
import GameController
|
||||||
|
import AuthenticationServices
|
||||||
|
|
||||||
final class MastodonPickServerViewController: UIViewController, NeedsDependency {
|
final class MastodonPickServerViewController: UIViewController, NeedsDependency {
|
||||||
|
|
||||||
|
@ -19,6 +20,11 @@ final class MastodonPickServerViewController: UIViewController, NeedsDependency
|
||||||
weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } }
|
weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } }
|
||||||
|
|
||||||
var viewModel: MastodonPickServerViewModel!
|
var viewModel: MastodonPickServerViewModel!
|
||||||
|
private(set) lazy var authenticationViewModel = AuthenticationViewModel(
|
||||||
|
context: context,
|
||||||
|
coordinator: coordinator,
|
||||||
|
isAuthenticationExist: false
|
||||||
|
)
|
||||||
|
|
||||||
private var expandServerDomainSet = Set<String>()
|
private var expandServerDomainSet = Set<String>()
|
||||||
|
|
||||||
|
@ -50,6 +56,8 @@ final class MastodonPickServerViewController: UIViewController, NeedsDependency
|
||||||
}()
|
}()
|
||||||
var nextStepButtonBottomLayoutConstraint: NSLayoutConstraint!
|
var nextStepButtonBottomLayoutConstraint: NSLayoutConstraint!
|
||||||
|
|
||||||
|
var mastodonAuthenticationController: MastodonAuthenticationController?
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
tableViewObservation = nil
|
tableViewObservation = nil
|
||||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
|
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
|
||||||
|
@ -182,7 +190,10 @@ extension MastodonPickServerViewController {
|
||||||
.assign(to: \.isEnabled, on: nextStepButton)
|
.assign(to: \.isEnabled, on: nextStepButton)
|
||||||
.store(in: &disposeBag)
|
.store(in: &disposeBag)
|
||||||
|
|
||||||
viewModel.error
|
Publishers.Merge(
|
||||||
|
viewModel.error,
|
||||||
|
authenticationViewModel.error
|
||||||
|
)
|
||||||
.compactMap { $0 }
|
.compactMap { $0 }
|
||||||
.receive(on: DispatchQueue.main)
|
.receive(on: DispatchQueue.main)
|
||||||
.sink { [weak self] error in
|
.sink { [weak self] error in
|
||||||
|
@ -198,7 +209,7 @@ extension MastodonPickServerViewController {
|
||||||
}
|
}
|
||||||
.store(in: &disposeBag)
|
.store(in: &disposeBag)
|
||||||
|
|
||||||
viewModel
|
authenticationViewModel
|
||||||
.authenticated
|
.authenticated
|
||||||
.flatMap { [weak self] (domain, user) -> AnyPublisher<Result<Bool, Error>, Never> in
|
.flatMap { [weak self] (domain, user) -> AnyPublisher<Result<Bool, Error>, Never> in
|
||||||
guard let self = self else { return Just(.success(false)).eraseToAnyPublisher() }
|
guard let self = self else { return Just(.success(false)).eraseToAnyPublisher() }
|
||||||
|
@ -217,7 +228,7 @@ extension MastodonPickServerViewController {
|
||||||
}
|
}
|
||||||
.store(in: &disposeBag)
|
.store(in: &disposeBag)
|
||||||
|
|
||||||
viewModel.isAuthenticating
|
authenticationViewModel.isAuthenticating
|
||||||
.receive(on: DispatchQueue.main)
|
.receive(on: DispatchQueue.main)
|
||||||
.sink { [weak self] isAuthenticating in
|
.sink { [weak self] isAuthenticating in
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
|
@ -273,11 +284,15 @@ extension MastodonPickServerViewController {
|
||||||
|
|
||||||
private func doSignIn() {
|
private func doSignIn() {
|
||||||
guard let server = viewModel.selectedServer.value else { return }
|
guard let server = viewModel.selectedServer.value else { return }
|
||||||
viewModel.isAuthenticating.send(true)
|
authenticationViewModel.isAuthenticating.send(true)
|
||||||
context.apiService.createApplication(domain: server.domain)
|
context.apiService.createApplication(domain: server.domain)
|
||||||
.tryMap { response -> MastodonPickServerViewModel.AuthenticateInfo in
|
.tryMap { response -> AuthenticationViewModel.AuthenticateInfo in
|
||||||
let application = response.value
|
let application = response.value
|
||||||
guard let info = MastodonPickServerViewModel.AuthenticateInfo(domain: server.domain, application: application) else {
|
guard let info = AuthenticationViewModel.AuthenticateInfo(
|
||||||
|
domain: server.domain,
|
||||||
|
application: application,
|
||||||
|
redirectURI: response.value.redirectURI ?? MastodonAuthenticationController.callbackURL
|
||||||
|
) else {
|
||||||
throw APIService.APIError.explicit(.badResponse)
|
throw APIService.APIError.explicit(.badResponse)
|
||||||
}
|
}
|
||||||
return info
|
return info
|
||||||
|
@ -285,7 +300,7 @@ extension MastodonPickServerViewController {
|
||||||
.receive(on: DispatchQueue.main)
|
.receive(on: DispatchQueue.main)
|
||||||
.sink { [weak self] completion in
|
.sink { [weak self] completion in
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
self.viewModel.isAuthenticating.send(false)
|
self.authenticationViewModel.isAuthenticating.send(false)
|
||||||
|
|
||||||
switch completion {
|
switch completion {
|
||||||
case .failure(let error):
|
case .failure(let error):
|
||||||
|
@ -296,15 +311,19 @@ extension MastodonPickServerViewController {
|
||||||
}
|
}
|
||||||
} receiveValue: { [weak self] info in
|
} receiveValue: { [weak self] info in
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
let mastodonPinBasedAuthenticationViewModel = MastodonPinBasedAuthenticationViewModel(authenticateURL: info.authorizeURL)
|
let authenticationController = MastodonAuthenticationController(
|
||||||
self.viewModel.authenticate(
|
context: self.context,
|
||||||
info: info,
|
authenticateURL: info.authorizeURL
|
||||||
pinCodePublisher: mastodonPinBasedAuthenticationViewModel.pinCodePublisher
|
|
||||||
)
|
)
|
||||||
self.viewModel.mastodonPinBasedAuthenticationViewController = self.coordinator.present(
|
|
||||||
scene: .mastodonPinBasedAuthentication(viewModel: mastodonPinBasedAuthenticationViewModel),
|
self.mastodonAuthenticationController = authenticationController
|
||||||
from: nil,
|
authenticationController.authenticationSession?.prefersEphemeralWebBrowserSession = true
|
||||||
transition: .modal(animated: true, completion: nil)
|
authenticationController.authenticationSession?.presentationContextProvider = self
|
||||||
|
authenticationController.authenticationSession?.start()
|
||||||
|
|
||||||
|
self.authenticationViewModel.authenticate(
|
||||||
|
info: info,
|
||||||
|
pinCodePublisher: authenticationController.pinCodePublisher
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.store(in: &disposeBag)
|
.store(in: &disposeBag)
|
||||||
|
@ -313,7 +332,7 @@ extension MastodonPickServerViewController {
|
||||||
private func doSignUp() {
|
private func doSignUp() {
|
||||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
|
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
|
||||||
guard let server = viewModel.selectedServer.value else { return }
|
guard let server = viewModel.selectedServer.value else { return }
|
||||||
viewModel.isAuthenticating.send(true)
|
authenticationViewModel.isAuthenticating.send(true)
|
||||||
|
|
||||||
context.apiService.instance(domain: server.domain)
|
context.apiService.instance(domain: server.domain)
|
||||||
.compactMap { [weak self] response -> AnyPublisher<MastodonPickServerViewModel.SignUpResponseFirst, Error>? in
|
.compactMap { [weak self] response -> AnyPublisher<MastodonPickServerViewModel.SignUpResponseFirst, Error>? in
|
||||||
|
@ -328,7 +347,10 @@ extension MastodonPickServerViewController {
|
||||||
.switchToLatest()
|
.switchToLatest()
|
||||||
.tryMap { response -> MastodonPickServerViewModel.SignUpResponseSecond in
|
.tryMap { response -> MastodonPickServerViewModel.SignUpResponseSecond in
|
||||||
let application = response.application.value
|
let application = response.application.value
|
||||||
guard let authenticateInfo = AuthenticationViewModel.AuthenticateInfo(domain: server.domain, application: application) else {
|
guard let authenticateInfo = AuthenticationViewModel.AuthenticateInfo(
|
||||||
|
domain: server.domain,
|
||||||
|
application: application
|
||||||
|
) else {
|
||||||
throw APIService.APIError.explicit(.badResponse)
|
throw APIService.APIError.explicit(.badResponse)
|
||||||
}
|
}
|
||||||
return MastodonPickServerViewModel.SignUpResponseSecond(instance: response.instance, authenticateInfo: authenticateInfo)
|
return MastodonPickServerViewModel.SignUpResponseSecond(instance: response.instance, authenticateInfo: authenticateInfo)
|
||||||
|
@ -340,7 +362,8 @@ extension MastodonPickServerViewController {
|
||||||
return self.context.apiService.applicationAccessToken(
|
return self.context.apiService.applicationAccessToken(
|
||||||
domain: server.domain,
|
domain: server.domain,
|
||||||
clientID: authenticateInfo.clientID,
|
clientID: authenticateInfo.clientID,
|
||||||
clientSecret: authenticateInfo.clientSecret
|
clientSecret: authenticateInfo.clientSecret,
|
||||||
|
redirectURI: authenticateInfo.redirectURI
|
||||||
)
|
)
|
||||||
.map { MastodonPickServerViewModel.SignUpResponseThird(instance: instance, authenticateInfo: authenticateInfo, applicationToken: $0) }
|
.map { MastodonPickServerViewModel.SignUpResponseThird(instance: instance, authenticateInfo: authenticateInfo, applicationToken: $0) }
|
||||||
.eraseToAnyPublisher()
|
.eraseToAnyPublisher()
|
||||||
|
@ -349,7 +372,7 @@ extension MastodonPickServerViewController {
|
||||||
.receive(on: DispatchQueue.main)
|
.receive(on: DispatchQueue.main)
|
||||||
.sink { [weak self] completion in
|
.sink { [weak self] completion in
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
self.viewModel.isAuthenticating.send(false)
|
self.authenticationViewModel.isAuthenticating.send(false)
|
||||||
|
|
||||||
switch completion {
|
switch completion {
|
||||||
case .failure(let error):
|
case .failure(let error):
|
||||||
|
@ -519,3 +542,10 @@ extension MastodonPickServerViewController: PickServerCellDelegate {
|
||||||
|
|
||||||
// MARK: - OnboardingViewControllerAppearance
|
// MARK: - OnboardingViewControllerAppearance
|
||||||
extension MastodonPickServerViewController: OnboardingViewControllerAppearance { }
|
extension MastodonPickServerViewController: OnboardingViewControllerAppearance { }
|
||||||
|
|
||||||
|
// MARK: - ASWebAuthenticationPresentationContextProviding
|
||||||
|
extension MastodonPickServerViewController: ASWebAuthenticationPresentationContextProviding {
|
||||||
|
func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor {
|
||||||
|
return view.window!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -58,15 +58,11 @@ class MastodonPickServerViewModel: NSObject {
|
||||||
let filteredIndexedServers = CurrentValueSubject<[Mastodon.Entity.Server], Never>([])
|
let filteredIndexedServers = CurrentValueSubject<[Mastodon.Entity.Server], Never>([])
|
||||||
let servers = CurrentValueSubject<[Mastodon.Entity.Server], Error>([])
|
let servers = CurrentValueSubject<[Mastodon.Entity.Server], Error>([])
|
||||||
let selectedServer = CurrentValueSubject<Mastodon.Entity.Server?, Never>(nil)
|
let selectedServer = CurrentValueSubject<Mastodon.Entity.Server?, Never>(nil)
|
||||||
let error = PassthroughSubject<Error, Never>()
|
let error = CurrentValueSubject<Error?, Never>(nil)
|
||||||
let authenticated = PassthroughSubject<(domain: String, account: Mastodon.Entity.Account), Never>()
|
|
||||||
let isAuthenticating = CurrentValueSubject<Bool, Never>(false)
|
|
||||||
|
|
||||||
let isLoadingIndexedServers = CurrentValueSubject<Bool, Never>(false)
|
let isLoadingIndexedServers = CurrentValueSubject<Bool, Never>(false)
|
||||||
let emptyStateViewState = CurrentValueSubject<EmptyStateViewState, Never>(.none)
|
let emptyStateViewState = CurrentValueSubject<EmptyStateViewState, Never>(.none)
|
||||||
|
|
||||||
var mastodonPinBasedAuthenticationViewController: UIViewController?
|
|
||||||
|
|
||||||
init(context: AppContext, mode: PickServerMode) {
|
init(context: AppContext, mode: PickServerMode) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.mode = mode
|
self.mode = mode
|
||||||
|
@ -234,156 +230,6 @@ extension MastodonPickServerViewModel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// MARK: - SignIn methods & structs
|
|
||||||
extension MastodonPickServerViewModel {
|
|
||||||
enum AuthenticationError: Error, LocalizedError {
|
|
||||||
case badCredentials
|
|
||||||
case registrationClosed
|
|
||||||
|
|
||||||
var errorDescription: String? {
|
|
||||||
switch self {
|
|
||||||
case .badCredentials: return "Bad Credentials"
|
|
||||||
case .registrationClosed: return "Registration Closed"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var failureReason: String? {
|
|
||||||
switch self {
|
|
||||||
case .badCredentials: return "Credentials invalid."
|
|
||||||
case .registrationClosed: return "Server disallow registration."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var helpAnchor: String? {
|
|
||||||
switch self {
|
|
||||||
case .badCredentials: return "Please try again."
|
|
||||||
case .registrationClosed: return "Please try another domain."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct AuthenticateInfo {
|
|
||||||
let domain: String
|
|
||||||
let clientID: String
|
|
||||||
let clientSecret: String
|
|
||||||
let authorizeURL: URL
|
|
||||||
|
|
||||||
init?(domain: String, application: Mastodon.Entity.Application) {
|
|
||||||
self.domain = domain
|
|
||||||
guard let clientID = application.clientID,
|
|
||||||
let clientSecret = application.clientSecret else { return nil }
|
|
||||||
self.clientID = clientID
|
|
||||||
self.clientSecret = clientSecret
|
|
||||||
self.authorizeURL = {
|
|
||||||
let query = Mastodon.API.OAuth.AuthorizeQuery(clientID: clientID)
|
|
||||||
let url = Mastodon.API.OAuth.authorizeURL(domain: domain, query: query)
|
|
||||||
return url
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func authenticate(info: AuthenticateInfo, pinCodePublisher: PassthroughSubject<String, Never>) {
|
|
||||||
pinCodePublisher
|
|
||||||
.handleEvents(receiveOutput: { [weak self] _ in
|
|
||||||
guard let self = self else { return }
|
|
||||||
// self.isAuthenticating.value = true
|
|
||||||
self.mastodonPinBasedAuthenticationViewController?.dismiss(animated: true, completion: nil)
|
|
||||||
self.mastodonPinBasedAuthenticationViewController = nil
|
|
||||||
})
|
|
||||||
.compactMap { [weak self] code -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Account>, Error>? in
|
|
||||||
guard let self = self else { return nil }
|
|
||||||
return self.context.apiService
|
|
||||||
.userAccessToken(
|
|
||||||
domain: info.domain,
|
|
||||||
clientID: info.clientID,
|
|
||||||
clientSecret: info.clientSecret,
|
|
||||||
code: code
|
|
||||||
)
|
|
||||||
.flatMap { response -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Account>, Error> in
|
|
||||||
let token = response.value
|
|
||||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: sign in success. Token: %s", ((#file as NSString).lastPathComponent), #line, #function, token.accessToken)
|
|
||||||
return Self.verifyAndSaveAuthentication(
|
|
||||||
context: self.context,
|
|
||||||
info: info,
|
|
||||||
userToken: token
|
|
||||||
)
|
|
||||||
}
|
|
||||||
.eraseToAnyPublisher()
|
|
||||||
}
|
|
||||||
.switchToLatest()
|
|
||||||
.receive(on: DispatchQueue.main)
|
|
||||||
.sink { [weak self] completion in
|
|
||||||
guard let self = self else { return }
|
|
||||||
switch completion {
|
|
||||||
case .failure(let error):
|
|
||||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: swap user access token swap fail: %s", ((#file as NSString).lastPathComponent), #line, #function, error.localizedDescription)
|
|
||||||
// self.isAuthenticating.value = false
|
|
||||||
self.error.send(error)
|
|
||||||
case .finished:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
} receiveValue: { [weak self] response in
|
|
||||||
guard let self = self else { return }
|
|
||||||
let account = response.value
|
|
||||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: user %s sign in success", ((#file as NSString).lastPathComponent), #line, #function, account.username)
|
|
||||||
|
|
||||||
self.authenticated.send((domain: info.domain, account: account))
|
|
||||||
}
|
|
||||||
.store(in: &self.disposeBag)
|
|
||||||
}
|
|
||||||
|
|
||||||
static func verifyAndSaveAuthentication(
|
|
||||||
context: AppContext,
|
|
||||||
info: AuthenticateInfo,
|
|
||||||
userToken: Mastodon.Entity.Token
|
|
||||||
) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Account>, Error> {
|
|
||||||
let authorization = Mastodon.API.OAuth.Authorization(accessToken: userToken.accessToken)
|
|
||||||
let managedObjectContext = context.backgroundManagedObjectContext
|
|
||||||
|
|
||||||
return context.apiService.accountVerifyCredentials(
|
|
||||||
domain: info.domain,
|
|
||||||
authorization: authorization
|
|
||||||
)
|
|
||||||
.flatMap { response -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Account>, Error> in
|
|
||||||
let account = response.value
|
|
||||||
let mastodonUserRequest = MastodonUser.sortedFetchRequest
|
|
||||||
mastodonUserRequest.predicate = MastodonUser.predicate(domain: info.domain, id: account.id)
|
|
||||||
mastodonUserRequest.fetchLimit = 1
|
|
||||||
guard let mastodonUser = try? managedObjectContext.fetch(mastodonUserRequest).first else {
|
|
||||||
return Fail(error: AuthenticationError.badCredentials).eraseToAnyPublisher()
|
|
||||||
}
|
|
||||||
|
|
||||||
let property = MastodonAuthentication.Property(
|
|
||||||
domain: info.domain,
|
|
||||||
userID: mastodonUser.id,
|
|
||||||
username: mastodonUser.username,
|
|
||||||
appAccessToken: userToken.accessToken, // TODO: swap app token
|
|
||||||
userAccessToken: userToken.accessToken,
|
|
||||||
clientID: info.clientID,
|
|
||||||
clientSecret: info.clientSecret
|
|
||||||
)
|
|
||||||
return managedObjectContext.performChanges {
|
|
||||||
_ = APIService.CoreData.createOrMergeMastodonAuthentication(
|
|
||||||
into: managedObjectContext,
|
|
||||||
for: mastodonUser,
|
|
||||||
in: info.domain,
|
|
||||||
property: property,
|
|
||||||
networkDate: response.networkDate
|
|
||||||
)
|
|
||||||
}
|
|
||||||
.tryMap { result in
|
|
||||||
switch result {
|
|
||||||
case .failure(let error): throw error
|
|
||||||
case .success: return response
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.eraseToAnyPublisher()
|
|
||||||
}
|
|
||||||
.eraseToAnyPublisher()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - SignUp methods & structs
|
// MARK: - SignUp methods & structs
|
||||||
extension MastodonPickServerViewModel {
|
extension MastodonPickServerViewModel {
|
||||||
struct SignUpResponseFirst {
|
struct SignUpResponseFirst {
|
||||||
|
|
|
@ -52,6 +52,8 @@ class PickServerSearchCell: UITableViewCell {
|
||||||
textField.clearButtonMode = .whileEditing
|
textField.clearButtonMode = .whileEditing
|
||||||
textField.autocapitalizationType = .none
|
textField.autocapitalizationType = .none
|
||||||
textField.autocorrectionType = .no
|
textField.autocorrectionType = .no
|
||||||
|
textField.returnKeyType = .done
|
||||||
|
textField.keyboardType = .URL
|
||||||
return textField
|
return textField
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
@ -78,6 +80,7 @@ extension PickServerSearchCell {
|
||||||
backgroundColor = Asset.Colors.Background.systemGroupedBackground.color
|
backgroundColor = Asset.Colors.Background.systemGroupedBackground.color
|
||||||
|
|
||||||
searchTextField.addTarget(self, action: #selector(textFieldDidChange(_:)), for: .editingChanged)
|
searchTextField.addTarget(self, action: #selector(textFieldDidChange(_:)), for: .editingChanged)
|
||||||
|
searchTextField.delegate = self
|
||||||
|
|
||||||
contentView.addSubview(bgView)
|
contentView.addSubview(bgView)
|
||||||
contentView.addSubview(textFieldBgView)
|
contentView.addSubview(textFieldBgView)
|
||||||
|
@ -107,3 +110,12 @@ extension PickServerSearchCell {
|
||||||
delegate?.pickServerSearchCell(self, searchTextDidChange: textField.text)
|
delegate?.pickServerSearchCell(self, searchTextDidChange: textField.text)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - UITextFieldDelegate
|
||||||
|
extension PickServerSearchCell: UITextFieldDelegate {
|
||||||
|
|
||||||
|
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
|
||||||
|
textField.resignFirstResponder()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,73 +0,0 @@
|
||||||
//
|
|
||||||
// MastodonPinBasedAuthenticationViewController.swift
|
|
||||||
// Mastodon
|
|
||||||
//
|
|
||||||
// Created by MainasuK Cirno on 2021/1/29.
|
|
||||||
//
|
|
||||||
|
|
||||||
import os.log
|
|
||||||
import UIKit
|
|
||||||
import Combine
|
|
||||||
import WebKit
|
|
||||||
|
|
||||||
final class MastodonPinBasedAuthenticationViewController: UIViewController, NeedsDependency {
|
|
||||||
|
|
||||||
weak var context: AppContext! { willSet { precondition(!isViewLoaded) } }
|
|
||||||
weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } }
|
|
||||||
|
|
||||||
var disposeBag = Set<AnyCancellable>()
|
|
||||||
var viewModel: MastodonPinBasedAuthenticationViewModel!
|
|
||||||
|
|
||||||
let webView: WKWebView = {
|
|
||||||
let configuration = WKWebViewConfiguration()
|
|
||||||
configuration.processPool = WKProcessPool()
|
|
||||||
let webView = WKWebView(frame: .zero, configuration: configuration)
|
|
||||||
return webView
|
|
||||||
}()
|
|
||||||
|
|
||||||
deinit {
|
|
||||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
|
|
||||||
|
|
||||||
// cleanup cookie
|
|
||||||
let httpCookieStore = webView.configuration.websiteDataStore.httpCookieStore
|
|
||||||
httpCookieStore.getAllCookies { cookies in
|
|
||||||
for cookie in cookies {
|
|
||||||
httpCookieStore.delete(cookie, completionHandler: nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
extension MastodonPinBasedAuthenticationViewController {
|
|
||||||
|
|
||||||
override func viewDidLoad() {
|
|
||||||
super.viewDidLoad()
|
|
||||||
|
|
||||||
title = "Authentication"
|
|
||||||
navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(MastodonPinBasedAuthenticationViewController.cancelBarButtonItemPressed(_:)))
|
|
||||||
|
|
||||||
webView.translatesAutoresizingMaskIntoConstraints = false
|
|
||||||
view.addSubview(webView)
|
|
||||||
NSLayoutConstraint.activate([
|
|
||||||
webView.topAnchor.constraint(equalTo: view.topAnchor),
|
|
||||||
webView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
|
|
||||||
webView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
|
|
||||||
webView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
|
|
||||||
])
|
|
||||||
|
|
||||||
let request = URLRequest(url: viewModel.authenticateURL)
|
|
||||||
webView.navigationDelegate = viewModel.navigationDelegate
|
|
||||||
webView.load(request)
|
|
||||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: authenticate via: %s", ((#file as NSString).lastPathComponent), #line, #function, viewModel.authenticateURL.debugDescription)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
extension MastodonPinBasedAuthenticationViewController {
|
|
||||||
|
|
||||||
@objc private func cancelBarButtonItemPressed(_ sender: UIBarButtonItem) {
|
|
||||||
dismiss(animated: true, completion: nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,40 +0,0 @@
|
||||||
//
|
|
||||||
// MastodonPinBasedAuthenticationViewModel.swift
|
|
||||||
// Mastodon
|
|
||||||
//
|
|
||||||
// Created by MainasuK Cirno on 2021/1/29.
|
|
||||||
//
|
|
||||||
|
|
||||||
import os.log
|
|
||||||
import Foundation
|
|
||||||
import Combine
|
|
||||||
import WebKit
|
|
||||||
|
|
||||||
final class MastodonPinBasedAuthenticationViewModel {
|
|
||||||
|
|
||||||
// input
|
|
||||||
let authenticateURL: URL
|
|
||||||
|
|
||||||
// output
|
|
||||||
let pinCodePublisher = PassthroughSubject<String, Never>()
|
|
||||||
private var navigationDelegateShim: MastodonPinBasedAuthenticationViewModelNavigationDelegateShim?
|
|
||||||
|
|
||||||
init(authenticateURL: URL) {
|
|
||||||
self.authenticateURL = authenticateURL
|
|
||||||
}
|
|
||||||
|
|
||||||
deinit {
|
|
||||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
extension MastodonPinBasedAuthenticationViewModel {
|
|
||||||
|
|
||||||
var navigationDelegate: WKNavigationDelegate {
|
|
||||||
let navigationDelegateShim = MastodonPinBasedAuthenticationViewModelNavigationDelegateShim(viewModel: self)
|
|
||||||
self.navigationDelegateShim = navigationDelegateShim
|
|
||||||
return navigationDelegateShim
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,41 +0,0 @@
|
||||||
//
|
|
||||||
// MastodonPinBasedAuthenticationViewModelNavigationDelegateShim.swift
|
|
||||||
// Mastodon
|
|
||||||
//
|
|
||||||
// Created by Cirno MainasuK on 2021/1/29.
|
|
||||||
//
|
|
||||||
|
|
||||||
import os.log
|
|
||||||
import Foundation
|
|
||||||
import WebKit
|
|
||||||
|
|
||||||
final class MastodonPinBasedAuthenticationViewModelNavigationDelegateShim: NSObject {
|
|
||||||
|
|
||||||
weak var viewModel: MastodonPinBasedAuthenticationViewModel?
|
|
||||||
|
|
||||||
init(viewModel: MastodonPinBasedAuthenticationViewModel) {
|
|
||||||
self.viewModel = viewModel
|
|
||||||
}
|
|
||||||
|
|
||||||
deinit {
|
|
||||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// MARK: - WKNavigationDelegate
|
|
||||||
extension MastodonPinBasedAuthenticationViewModelNavigationDelegateShim: WKNavigationDelegate {
|
|
||||||
|
|
||||||
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
|
|
||||||
guard let url = webView.url,
|
|
||||||
let components = URLComponents(url: url, resolvingAgainstBaseURL: false),
|
|
||||||
let codeQueryItem = components.queryItems?.first(where: { $0.name == "code" }),
|
|
||||||
let code = codeQueryItem.value else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
viewModel?.pinCodePublisher.send(code)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
|
@ -32,8 +32,6 @@ final class AuthenticationViewModel {
|
||||||
let authenticated = PassthroughSubject<(domain: String, account: Mastodon.Entity.Account), Never>()
|
let authenticated = PassthroughSubject<(domain: String, account: Mastodon.Entity.Account), Never>()
|
||||||
let error = CurrentValueSubject<Error?, Never>(nil)
|
let error = CurrentValueSubject<Error?, Never>(nil)
|
||||||
|
|
||||||
var mastodonPinBasedAuthenticationViewController: UIViewController?
|
|
||||||
|
|
||||||
init(context: AppContext, coordinator: SceneCoordinator, isAuthenticationExist: Bool) {
|
init(context: AppContext, coordinator: SceneCoordinator, isAuthenticationExist: Bool) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.coordinator = coordinator
|
self.coordinator = coordinator
|
||||||
|
@ -118,18 +116,24 @@ extension AuthenticationViewModel {
|
||||||
let clientID: String
|
let clientID: String
|
||||||
let clientSecret: String
|
let clientSecret: String
|
||||||
let authorizeURL: URL
|
let authorizeURL: URL
|
||||||
|
let redirectURI: String
|
||||||
|
|
||||||
init?(domain: String, application: Mastodon.Entity.Application) {
|
init?(
|
||||||
|
domain: String,
|
||||||
|
application: Mastodon.Entity.Application,
|
||||||
|
redirectURI: String = MastodonAuthenticationController.callbackURL
|
||||||
|
) {
|
||||||
self.domain = domain
|
self.domain = domain
|
||||||
guard let clientID = application.clientID,
|
guard let clientID = application.clientID,
|
||||||
let clientSecret = application.clientSecret else { return nil }
|
let clientSecret = application.clientSecret else { return nil }
|
||||||
self.clientID = clientID
|
self.clientID = clientID
|
||||||
self.clientSecret = clientSecret
|
self.clientSecret = clientSecret
|
||||||
self.authorizeURL = {
|
self.authorizeURL = {
|
||||||
let query = Mastodon.API.OAuth.AuthorizeQuery(clientID: clientID)
|
let query = Mastodon.API.OAuth.AuthorizeQuery(clientID: clientID, redirectURI: redirectURI)
|
||||||
let url = Mastodon.API.OAuth.authorizeURL(domain: domain, query: query)
|
let url = Mastodon.API.OAuth.authorizeURL(domain: domain, query: query)
|
||||||
return url
|
return url
|
||||||
}()
|
}()
|
||||||
|
self.redirectURI = redirectURI
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -138,8 +142,6 @@ extension AuthenticationViewModel {
|
||||||
.handleEvents(receiveOutput: { [weak self] _ in
|
.handleEvents(receiveOutput: { [weak self] _ in
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
self.isAuthenticating.value = true
|
self.isAuthenticating.value = true
|
||||||
self.mastodonPinBasedAuthenticationViewController?.dismiss(animated: true, completion: nil)
|
|
||||||
self.mastodonPinBasedAuthenticationViewController = nil
|
|
||||||
})
|
})
|
||||||
.compactMap { [weak self] code -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Account>, Error>? in
|
.compactMap { [weak self] code -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Account>, Error>? in
|
||||||
guard let self = self else { return nil }
|
guard let self = self else { return nil }
|
||||||
|
@ -148,6 +150,7 @@ extension AuthenticationViewModel {
|
||||||
domain: info.domain,
|
domain: info.domain,
|
||||||
clientID: info.clientID,
|
clientID: info.clientID,
|
||||||
clientSecret: info.clientSecret,
|
clientSecret: info.clientSecret,
|
||||||
|
redirectURI: info.redirectURI,
|
||||||
code: code
|
code: code
|
||||||
)
|
)
|
||||||
.flatMap { response -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Account>, Error> in
|
.flatMap { response -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Account>, Error> in
|
||||||
|
|
|
@ -0,0 +1,75 @@
|
||||||
|
//
|
||||||
|
// MastodonAuthenticationController.swift
|
||||||
|
// Mastodon
|
||||||
|
//
|
||||||
|
// Created by MainasuK Cirno on 2021-6-4.
|
||||||
|
//
|
||||||
|
|
||||||
|
import os.log
|
||||||
|
import UIKit
|
||||||
|
import Combine
|
||||||
|
import AuthenticationServices
|
||||||
|
|
||||||
|
final class MastodonAuthenticationController {
|
||||||
|
|
||||||
|
static let callbackURLScheme = "mastodon"
|
||||||
|
static let callbackURL = "mastodon://joinmastodon.org/oauth"
|
||||||
|
|
||||||
|
var disposeBag = Set<AnyCancellable>()
|
||||||
|
|
||||||
|
// input
|
||||||
|
var context: AppContext!
|
||||||
|
let authenticateURL: URL
|
||||||
|
var authenticationSession: ASWebAuthenticationSession?
|
||||||
|
|
||||||
|
// output
|
||||||
|
let isAuthenticating = CurrentValueSubject<Bool, Never>(false)
|
||||||
|
let error = CurrentValueSubject<Error?, Never>(nil)
|
||||||
|
let pinCodePublisher = PassthroughSubject<String, Never>()
|
||||||
|
|
||||||
|
init(
|
||||||
|
context: AppContext,
|
||||||
|
authenticateURL: URL
|
||||||
|
) {
|
||||||
|
self.context = context
|
||||||
|
self.authenticateURL = authenticateURL
|
||||||
|
|
||||||
|
authentication()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension MastodonAuthenticationController {
|
||||||
|
private func authentication() {
|
||||||
|
authenticationSession = ASWebAuthenticationSession(
|
||||||
|
url: authenticateURL,
|
||||||
|
callbackURLScheme: MastodonAuthenticationController.callbackURLScheme
|
||||||
|
) { [weak self] callback, error in
|
||||||
|
guard let self = self else { return }
|
||||||
|
os_log("%{public}s[%{public}ld], %{public}s: callback: %s, error: %s", ((#file as NSString).lastPathComponent), #line, #function, callback?.debugDescription ?? "<nil>", error.debugDescription)
|
||||||
|
|
||||||
|
if let error = error {
|
||||||
|
if let error = error as? ASWebAuthenticationSessionError {
|
||||||
|
if error.errorCode == ASWebAuthenticationSessionError.canceledLogin.rawValue {
|
||||||
|
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: user cancel authentication", ((#file as NSString).lastPathComponent), #line, #function)
|
||||||
|
self.isAuthenticating.value = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.isAuthenticating.value = false
|
||||||
|
self.error.value = error
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let url = callback,
|
||||||
|
let components = URLComponents(url: url, resolvingAgainstBaseURL: false),
|
||||||
|
let codeQueryItem = components.queryItems?.first(where: { $0.name == "code" }),
|
||||||
|
let code = codeQueryItem.value else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
self.pinCodePublisher.send(code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -94,7 +94,7 @@ class SettingsViewController: UIViewController, NeedsDependency {
|
||||||
tableView.translatesAutoresizingMaskIntoConstraints = false
|
tableView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
tableView.delegate = self
|
tableView.delegate = self
|
||||||
tableView.rowHeight = UITableView.automaticDimension
|
tableView.rowHeight = UITableView.automaticDimension
|
||||||
tableView.backgroundColor = Asset.Colors.Background.systemGroupedBackground.color
|
tableView.backgroundColor = .clear
|
||||||
|
|
||||||
tableView.register(SettingsAppearanceTableViewCell.self, forCellReuseIdentifier: String(describing: SettingsAppearanceTableViewCell.self))
|
tableView.register(SettingsAppearanceTableViewCell.self, forCellReuseIdentifier: String(describing: SettingsAppearanceTableViewCell.self))
|
||||||
tableView.register(SettingsToggleTableViewCell.self, forCellReuseIdentifier: String(describing: SettingsToggleTableViewCell.self))
|
tableView.register(SettingsToggleTableViewCell.self, forCellReuseIdentifier: String(describing: SettingsToggleTableViewCell.self))
|
||||||
|
@ -185,7 +185,14 @@ class SettingsViewController: UIViewController, NeedsDependency {
|
||||||
}
|
}
|
||||||
|
|
||||||
private func setupView() {
|
private func setupView() {
|
||||||
view.backgroundColor = Asset.Colors.Background.secondarySystemBackground.color
|
view.backgroundColor = UIColor(dynamicProvider: { traitCollection in
|
||||||
|
switch traitCollection.userInterfaceLevel {
|
||||||
|
case .elevated where traitCollection.userInterfaceStyle == .dark:
|
||||||
|
return Asset.Colors.Background.systemElevatedBackground.color
|
||||||
|
default:
|
||||||
|
return Asset.Colors.Background.secondarySystemBackground.color
|
||||||
|
}
|
||||||
|
})
|
||||||
setupNavigation()
|
setupNavigation()
|
||||||
|
|
||||||
view.addSubview(tableView)
|
view.addSubview(tableView)
|
||||||
|
|
|
@ -176,7 +176,7 @@ class SettingsAppearanceTableViewCell: UITableViewCell {
|
||||||
|
|
||||||
// MARK: Private methods
|
// MARK: Private methods
|
||||||
private func setupUI() {
|
private func setupUI() {
|
||||||
backgroundColor = Asset.Colors.Background.systemGroupedBackground.color
|
backgroundColor = .clear
|
||||||
selectionStyle = .none
|
selectionStyle = .none
|
||||||
contentView.addSubview(stackView)
|
contentView.addSubview(stackView)
|
||||||
|
|
||||||
|
|
|
@ -39,7 +39,8 @@ class SettingsSectionHeader: UIView {
|
||||||
init(frame: CGRect, customView: UIView? = nil) {
|
init(frame: CGRect, customView: UIView? = nil) {
|
||||||
super.init(frame: frame)
|
super.init(frame: frame)
|
||||||
|
|
||||||
backgroundColor = Asset.Colors.Background.systemGroupedBackground.color
|
backgroundColor = .clear
|
||||||
|
|
||||||
stackView.addArrangedSubview(titleLabel)
|
stackView.addArrangedSubview(titleLabel)
|
||||||
if let view = customView {
|
if let view = customView {
|
||||||
stackView.addArrangedSubview(view)
|
stackView.addArrangedSubview(view)
|
||||||
|
|
|
@ -46,7 +46,7 @@ struct MosaicMeta {
|
||||||
let blurhash: String?
|
let blurhash: String?
|
||||||
let altText: String?
|
let altText: String?
|
||||||
|
|
||||||
let workingQueue = DispatchQueue(label: "org.joinmastodon.Mastodon.MosaicMeta.working-queue", qos: .userInitiated, attributes: .concurrent)
|
let workingQueue = DispatchQueue(label: "org.joinmastodon.app.MosaicMeta.working-queue", qos: .userInitiated, attributes: .concurrent)
|
||||||
|
|
||||||
func blurhashImagePublisher() -> AnyPublisher<UIImage?, Never> {
|
func blurhashImagePublisher() -> AnyPublisher<UIImage?, Never> {
|
||||||
return Future { promise in
|
return Future { promise in
|
||||||
|
|
|
@ -14,7 +14,7 @@ import UIKit
|
||||||
final class VideoPlayerViewModel {
|
final class VideoPlayerViewModel {
|
||||||
var disposeBag = Set<AnyCancellable>()
|
var disposeBag = Set<AnyCancellable>()
|
||||||
|
|
||||||
static let appWillPlayVideoNotification = NSNotification.Name(rawValue: "org.joinmastodon.Mastodon.video-playback-service.appWillPlayVideo")
|
static let appWillPlayVideoNotification = NSNotification.Name(rawValue: "org.joinmastodon.app.video-playback-service.appWillPlayVideo")
|
||||||
// input
|
// input
|
||||||
let previewImageURL: URL?
|
let previewImageURL: URL?
|
||||||
let videoURL: URL
|
let videoURL: URL
|
||||||
|
|
|
@ -20,7 +20,11 @@ extension APIService {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
func createApplication(domain: String) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Application>, Error> {
|
func createApplication(domain: String) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Application>, Error> {
|
||||||
let query = Mastodon.API.App.CreateQuery(clientName: APIService.clientName, website: nil)
|
let query = Mastodon.API.App.CreateQuery(
|
||||||
|
clientName: APIService.clientName,
|
||||||
|
redirectURIs: MastodonAuthenticationController.callbackURL,
|
||||||
|
website: nil
|
||||||
|
)
|
||||||
return Mastodon.API.App.create(
|
return Mastodon.API.App.create(
|
||||||
session: session,
|
session: session,
|
||||||
domain: domain,
|
domain: domain,
|
||||||
|
|
|
@ -17,11 +17,13 @@ extension APIService {
|
||||||
domain: String,
|
domain: String,
|
||||||
clientID: String,
|
clientID: String,
|
||||||
clientSecret: String,
|
clientSecret: String,
|
||||||
|
redirectURI: String,
|
||||||
code: String
|
code: String
|
||||||
) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Token>, Error> {
|
) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Token>, Error> {
|
||||||
let query = Mastodon.API.OAuth.AccessTokenQuery(
|
let query = Mastodon.API.OAuth.AccessTokenQuery(
|
||||||
clientID: clientID,
|
clientID: clientID,
|
||||||
clientSecret: clientSecret,
|
clientSecret: clientSecret,
|
||||||
|
redirectURI: redirectURI,
|
||||||
code: code,
|
code: code,
|
||||||
grantType: "authorization_code"
|
grantType: "authorization_code"
|
||||||
)
|
)
|
||||||
|
@ -35,11 +37,13 @@ extension APIService {
|
||||||
func applicationAccessToken(
|
func applicationAccessToken(
|
||||||
domain: String,
|
domain: String,
|
||||||
clientID: String,
|
clientID: String,
|
||||||
clientSecret: String
|
clientSecret: String,
|
||||||
|
redirectURI: String
|
||||||
) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Token>, Error> {
|
) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Token>, Error> {
|
||||||
let query = Mastodon.API.OAuth.AccessTokenQuery(
|
let query = Mastodon.API.OAuth.AccessTokenQuery(
|
||||||
clientID: clientID,
|
clientID: clientID,
|
||||||
clientSecret: clientSecret,
|
clientSecret: clientSecret,
|
||||||
|
redirectURI: redirectURI,
|
||||||
code: nil,
|
code: nil,
|
||||||
grantType: "client_credentials"
|
grantType: "client_credentials"
|
||||||
)
|
)
|
||||||
|
|
|
@ -14,7 +14,7 @@ import os.log
|
||||||
|
|
||||||
final class AudioPlaybackService: NSObject {
|
final class AudioPlaybackService: NSObject {
|
||||||
|
|
||||||
static let appWillPlayAudioNotification = NSNotification.Name(rawValue: "org.joinmastodon.Mastodon.audio-playback-service.appWillPlayAudio")
|
static let appWillPlayAudioNotification = NSNotification.Name(rawValue: "org.joinmastodon.app.audio-playback-service.appWillPlayAudio")
|
||||||
|
|
||||||
var disposeBag = Set<AnyCancellable>()
|
var disposeBag = Set<AnyCancellable>()
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,7 @@ final class EmojiService {
|
||||||
|
|
||||||
weak var apiService: APIService?
|
weak var apiService: APIService?
|
||||||
|
|
||||||
let workingQueue = DispatchQueue(label: "org.joinmastodon.Mastodon.EmojiService.working-queue")
|
let workingQueue = DispatchQueue(label: "org.joinmastodon.app.EmojiService.working-queue")
|
||||||
private(set) var customEmojiViewModelDict: [String: CustomEmojiViewModel] = [:]
|
private(set) var customEmojiViewModelDict: [String: CustomEmojiViewModel] = [:]
|
||||||
|
|
||||||
init(apiService: APIService) {
|
init(apiService: APIService) {
|
||||||
|
|
|
@ -52,10 +52,10 @@ final class MastodonAttachmentService {
|
||||||
init(
|
init(
|
||||||
context: AppContext,
|
context: AppContext,
|
||||||
pickerResult: PHPickerResult,
|
pickerResult: PHPickerResult,
|
||||||
initalAuthenticationBox: AuthenticationService.MastodonAuthenticationBox?
|
initialAuthenticationBox: AuthenticationService.MastodonAuthenticationBox?
|
||||||
) {
|
) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.authenticationBox = initalAuthenticationBox
|
self.authenticationBox = initialAuthenticationBox
|
||||||
// end init
|
// end init
|
||||||
|
|
||||||
setupServiceObserver()
|
setupServiceObserver()
|
||||||
|
@ -90,10 +90,10 @@ final class MastodonAttachmentService {
|
||||||
init(
|
init(
|
||||||
context: AppContext,
|
context: AppContext,
|
||||||
image: UIImage,
|
image: UIImage,
|
||||||
initalAuthenticationBox: AuthenticationService.MastodonAuthenticationBox?
|
initialAuthenticationBox: AuthenticationService.MastodonAuthenticationBox?
|
||||||
) {
|
) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.authenticationBox = initalAuthenticationBox
|
self.authenticationBox = initialAuthenticationBox
|
||||||
// end init
|
// end init
|
||||||
|
|
||||||
setupServiceObserver()
|
setupServiceObserver()
|
||||||
|
@ -105,10 +105,10 @@ final class MastodonAttachmentService {
|
||||||
init(
|
init(
|
||||||
context: AppContext,
|
context: AppContext,
|
||||||
documentURL: URL,
|
documentURL: URL,
|
||||||
initalAuthenticationBox: AuthenticationService.MastodonAuthenticationBox?
|
initialAuthenticationBox: AuthenticationService.MastodonAuthenticationBox?
|
||||||
) {
|
) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.authenticationBox = initalAuthenticationBox
|
self.authenticationBox = initialAuthenticationBox
|
||||||
// end init
|
// end init
|
||||||
|
|
||||||
setupServiceObserver()
|
setupServiceObserver()
|
||||||
|
@ -213,7 +213,7 @@ extension MastodonAttachmentService: Equatable, Hashable {
|
||||||
extension MastodonAttachmentService {
|
extension MastodonAttachmentService {
|
||||||
|
|
||||||
private static func createWorkingQueue() -> DispatchQueue {
|
private static func createWorkingQueue() -> DispatchQueue {
|
||||||
return DispatchQueue(label: "org.joinmastodon.Mastodon.MastodonAttachmentService.\(UUID().uuidString)")
|
return DispatchQueue(label: "org.joinmastodon.app.MastodonAttachmentService.\(UUID().uuidString)")
|
||||||
}
|
}
|
||||||
|
|
||||||
static func loadAttachment(url: URL) -> AnyPublisher<Mastodon.Query.MediaAttachment, Error> {
|
static func loadAttachment(url: URL) -> AnyPublisher<Mastodon.Query.MediaAttachment, Error> {
|
||||||
|
|
|
@ -17,7 +17,7 @@ final class NotificationService {
|
||||||
|
|
||||||
var disposeBag = Set<AnyCancellable>()
|
var disposeBag = Set<AnyCancellable>()
|
||||||
|
|
||||||
let workingQueue = DispatchQueue(label: "org.joinmastodon.Mastodon.NotificationService.working-queue")
|
let workingQueue = DispatchQueue(label: "org.joinmastodon.app.NotificationService.working-queue")
|
||||||
|
|
||||||
// input
|
// input
|
||||||
weak var apiService: APIService?
|
weak var apiService: APIService?
|
||||||
|
|
|
@ -16,7 +16,7 @@ final class StatusPrefetchingService {
|
||||||
|
|
||||||
typealias TaskID = String
|
typealias TaskID = String
|
||||||
|
|
||||||
let workingQueue = DispatchQueue(label: "org.joinmastodon.Mastodon.StatusPrefetchingService.working-queue")
|
let workingQueue = DispatchQueue(label: "org.joinmastodon.app.StatusPrefetchingService.working-queue")
|
||||||
|
|
||||||
var disposeBag = Set<AnyCancellable>()
|
var disposeBag = Set<AnyCancellable>()
|
||||||
private(set) var statusPrefetchingDisposeBagDict: [TaskID: AnyCancellable] = [:]
|
private(set) var statusPrefetchingDisposeBagDict: [TaskID: AnyCancellable] = [:]
|
||||||
|
|
|
@ -16,7 +16,7 @@ final class StatusPublishService {
|
||||||
|
|
||||||
var disposeBag = Set<AnyCancellable>()
|
var disposeBag = Set<AnyCancellable>()
|
||||||
|
|
||||||
let workingQueue = DispatchQueue(label: "org.joinmastodon.Mastodon.StatusPublishService.working-queue")
|
let workingQueue = DispatchQueue(label: "org.joinmastodon.app.StatusPublishService.working-queue")
|
||||||
|
|
||||||
// input
|
// input
|
||||||
var viewModels = CurrentValueSubject<[ComposeViewModel], Never>([]) // use strong reference to retain the view models
|
var viewModels = CurrentValueSubject<[ComposeViewModel], Never>([]) // use strong reference to retain the view models
|
||||||
|
|
|
@ -14,7 +14,7 @@ import os.log
|
||||||
final class VideoPlaybackService {
|
final class VideoPlaybackService {
|
||||||
var disposeBag = Set<AnyCancellable>()
|
var disposeBag = Set<AnyCancellable>()
|
||||||
|
|
||||||
let workingQueue = DispatchQueue(label: "org.joinmastodon.Mastodon.VideoPlaybackService.working-queue")
|
let workingQueue = DispatchQueue(label: "org.joinmastodon.app.VideoPlaybackService.working-queue")
|
||||||
private(set) var viewPlayerViewModelDict: [URL: VideoPlayerViewModel] = [:]
|
private(set) var viewPlayerViewModelDict: [URL: VideoPlayerViewModel] = [:]
|
||||||
|
|
||||||
// only for video kind
|
// only for video kind
|
||||||
|
|
|
@ -111,7 +111,7 @@ extension AppContext {
|
||||||
return formatter
|
return formatter
|
||||||
}()
|
}()
|
||||||
|
|
||||||
private static let purgeCacheWorkingQueue = DispatchQueue(label: "org.joinmastodon.Mastodon.AppContext.purgeCacheWorkingQueue")
|
private static let purgeCacheWorkingQueue = DispatchQueue(label: "org.joinmastodon.app.AppContext.purgeCacheWorkingQueue")
|
||||||
|
|
||||||
func purgeCache() -> AnyPublisher<ByteCount, Never> {
|
func purgeCache() -> AnyPublisher<ByteCount, Never> {
|
||||||
Publishers.MergeMany([
|
Publishers.MergeMany([
|
||||||
|
|
|
@ -103,7 +103,7 @@ extension Mastodon.API.App {
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
clientName: String,
|
clientName: String,
|
||||||
redirectURIs: String = "urn:ietf:wg:oauth:2.0:oob",
|
redirectURIs: String,
|
||||||
scopes: String? = "read write follow push",
|
scopes: String? = "read write follow push",
|
||||||
website: String?
|
website: String?
|
||||||
) {
|
) {
|
||||||
|
|
|
@ -139,7 +139,7 @@ extension Mastodon.API.OAuth {
|
||||||
forceLogin: String? = nil,
|
forceLogin: String? = nil,
|
||||||
responseType: String = "code",
|
responseType: String = "code",
|
||||||
clientID: String,
|
clientID: String,
|
||||||
redirectURI: String = "urn:ietf:wg:oauth:2.0:oob",
|
redirectURI: String,
|
||||||
scope: String? = "read write follow push"
|
scope: String? = "read write follow push"
|
||||||
) {
|
) {
|
||||||
self.forceLogin = forceLogin
|
self.forceLogin = forceLogin
|
||||||
|
@ -166,7 +166,7 @@ extension Mastodon.API.OAuth {
|
||||||
public init(
|
public init(
|
||||||
clientID: String,
|
clientID: String,
|
||||||
clientSecret: String,
|
clientSecret: String,
|
||||||
redirectURI: String = "urn:ietf:wg:oauth:2.0:oob",
|
redirectURI: String,
|
||||||
scope: String? = "read write follow push",
|
scope: String? = "read write follow push",
|
||||||
code: String?,
|
code: String?,
|
||||||
grantType: String
|
grantType: String
|
||||||
|
|
|
@ -26,7 +26,7 @@ final class SerialStream: NSObject {
|
||||||
private var buffer: UnsafeMutablePointer<UInt8>
|
private var buffer: UnsafeMutablePointer<UInt8>
|
||||||
private var canWrite = false
|
private var canWrite = false
|
||||||
|
|
||||||
private let workingQueue = DispatchQueue(label: "org.joinmastodon.Mastodon.SerialStream.\(UUID().uuidString)")
|
private let workingQueue = DispatchQueue(label: "org.joinmastodon.app.SerialStream.\(UUID().uuidString)")
|
||||||
|
|
||||||
// bound pair stream
|
// bound pair stream
|
||||||
private(set) lazy var boundStreams: Streams = {
|
private(set) lazy var boundStreams: Streams = {
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<dict>
|
<dict>
|
||||||
<key>com.apple.security.application-groups</key>
|
<key>com.apple.security.application-groups</key>
|
||||||
<array>
|
<array>
|
||||||
<string>group.org.joinmastodon.mastodon-temp</string>
|
<string>group.org.joinmastodon.app</string>
|
||||||
</array>
|
</array>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|
Loading…
Reference in New Issue