From 8e749fd75b65fff7b43619ec9b7d3a12e844e20c Mon Sep 17 00:00:00 2001 From: CMK Date: Fri, 16 Jul 2021 16:21:47 +0800 Subject: [PATCH 01/11] feat: add share action extension --- CoreDataStack/CoreDataStack.swift | 16 +- Mastodon.xcodeproj/project.pbxproj | 220 +++++++++++++++++- .../xcschemes/xcschememanagement.plist | 9 +- Mastodon/Extension/UIImage.swift | 11 - .../Scene/Compose/ComposeViewController.swift | 1 + .../HomeTimelineNavigationBarTitleView.swift | 1 + .../ProfileRelationshipActionButton.swift | 1 + MastodonSDK/Package.swift | 14 ++ .../Sources/MastodonExtension/UIImage.swift | 19 ++ .../MastodonUI}/RoundedEdgesButton.swift | 6 +- MastodonSDK/Sources/MastodonUI/import.swift | 1 + .../Base.lproj/MainInterface.storyboard | 45 ++++ ShareActionExtension/Info.plist | 45 ++++ .../ShareViewController.swift | 102 ++++++++ ShareActionExtension/ShareViewModel.swift | 99 ++++++++ 15 files changed, 564 insertions(+), 26 deletions(-) create mode 100644 MastodonSDK/Sources/MastodonExtension/UIImage.swift rename {Mastodon/Scene/Share/View/Button => MastodonSDK/Sources/MastodonUI}/RoundedEdgesButton.swift (71%) create mode 100644 MastodonSDK/Sources/MastodonUI/import.swift create mode 100644 ShareActionExtension/Base.lproj/MainInterface.storyboard create mode 100644 ShareActionExtension/Info.plist create mode 100644 ShareActionExtension/ShareViewController.swift create mode 100644 ShareActionExtension/ShareViewModel.swift diff --git a/CoreDataStack/CoreDataStack.swift b/CoreDataStack/CoreDataStack.swift index 64bf9c85..2d6224e7 100644 --- a/CoreDataStack/CoreDataStack.swift +++ b/CoreDataStack/CoreDataStack.swift @@ -7,12 +7,14 @@ import os import Foundation +import Combine import CoreData import AppShared public final class CoreDataStack { private(set) var storeDescriptions: [NSPersistentStoreDescription] + public let didFinishLoad = CurrentValueSubject(false) init(persistentStoreDescriptions storeDescriptions: [NSPersistentStoreDescription]) { self.storeDescriptions = storeDescriptions @@ -33,7 +35,10 @@ public final class CoreDataStack { */ let container = CoreDataStack.persistentContainer() CoreDataStack.configure(persistentContainer: container, storeDescriptions: storeDescriptions) - CoreDataStack.load(persistentContainer: container) + CoreDataStack.load(persistentContainer: container) { [weak self] in + guard let self = self else { return } + self.didFinishLoad.value = true + } return container }() @@ -52,7 +57,7 @@ public final class CoreDataStack { container.persistentStoreDescriptions = storeDescriptions } - static func load(persistentContainer container: NSPersistentContainer) { + static func load(persistentContainer container: NSPersistentContainer, callback: @escaping () -> Void) { container.loadPersistentStores(completionHandler: { (storeDescription, error) in if let error = error as NSError? { // Replace this implementation with code to handle the error appropriately. @@ -85,6 +90,8 @@ public final class CoreDataStack { container.viewContext.automaticallyMergesChangesFromParent = true os_log("%{public}s[%{public}ld], %{public}s: %s", ((#file as NSString).lastPathComponent), #line, #function, storeDescription.debugDescription) + + callback() }) } @@ -96,7 +103,10 @@ extension CoreDataStack { let oldStoreURL = persistentContainer.persistentStoreCoordinator.url(for: persistentContainer.persistentStoreCoordinator.persistentStores.first!) try! persistentContainer.persistentStoreCoordinator.destroyPersistentStore(at: oldStoreURL, ofType: NSSQLiteStoreType, options: nil) - CoreDataStack.load(persistentContainer: persistentContainer) + CoreDataStack.load(persistentContainer: persistentContainer) { [weak self] in + guard let self = self else { return } + self.didFinishLoad.value = true + } } } diff --git a/Mastodon.xcodeproj/project.pbxproj b/Mastodon.xcodeproj/project.pbxproj index 69d8176a..f82cb5ab 100644 --- a/Mastodon.xcodeproj/project.pbxproj +++ b/Mastodon.xcodeproj/project.pbxproj @@ -423,7 +423,6 @@ DB9D7C21269824B80054B3DF /* APIService+Filter.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB9D7C20269824B80054B3DF /* APIService+Filter.swift */; }; DB9E0D6F25EE008500CFDD76 /* UIInterpolatingMotionEffect.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB9E0D6E25EE008500CFDD76 /* UIInterpolatingMotionEffect.swift */; }; DBA088DF26958164003EB4B2 /* UserFetchedResultsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBA088DE26958164003EB4B2 /* UserFetchedResultsController.swift */; }; - DBA0A10925FB3C2B0079C110 /* RoundedEdgesButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBA0A10825FB3C2B0079C110 /* RoundedEdgesButton.swift */; }; DBA0A11325FB3FC10079C110 /* ComposeToolbarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBA0A11225FB3FC10079C110 /* ComposeToolbarView.swift */; }; DBA1DB80268F84F80052DB59 /* NotificationType.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBA1DB7F268F84F80052DB59 /* NotificationType.swift */; }; DBA465932696B495002B41DB /* APIService+WebFinger.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBA465922696B495002B41DB /* APIService+WebFinger.swift */; }; @@ -481,6 +480,17 @@ DBBF1DC7265251D400E5B703 /* AutoCompleteViewModel+State.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBF1DC6265251D400E5B703 /* AutoCompleteViewModel+State.swift */; }; DBBF1DC92652538500E5B703 /* AutoCompleteSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBF1DC82652538500E5B703 /* AutoCompleteSection.swift */; }; DBBF1DCB2652539E00E5B703 /* AutoCompleteItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBF1DCA2652539E00E5B703 /* AutoCompleteItem.swift */; }; + DBC6461526A170AB00B0E31B /* ShareViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBC6461426A170AB00B0E31B /* ShareViewController.swift */; }; + DBC6461826A170AB00B0E31B /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = DBC6461626A170AB00B0E31B /* MainInterface.storyboard */; }; + DBC6461C26A170AB00B0E31B /* ShareActionExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = DBC6461226A170AB00B0E31B /* ShareActionExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + DBC6462326A1712000B0E31B /* ShareViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBC6462226A1712000B0E31B /* ShareViewModel.swift */; }; + DBC6462526A1720B00B0E31B /* MastodonUI in Frameworks */ = {isa = PBXBuildFile; productRef = DBC6462426A1720B00B0E31B /* MastodonUI */; }; + DBC6462626A1736000B0E31B /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = DB564BCE269F2F83001E39A7 /* Localizable.stringsdict */; }; + DBC6462726A1736000B0E31B /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = DB3D100F25BAA75E00EAA174 /* Localizable.strings */; }; + DBC6462826A1736300B0E31B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DB427DDE25BAA00100D1B89D /* Assets.xcassets */; }; + DBC6462926A1736700B0E31B /* Strings.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB98338525C945ED00AD9700 /* Strings.swift */; }; + DBC6462B26A1738900B0E31B /* MastodonUI in Frameworks */ = {isa = PBXBuildFile; productRef = DBC6462A26A1738900B0E31B /* MastodonUI */; }; + DBC6462C26A176B000B0E31B /* Assets.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB98338625C945ED00AD9700 /* Assets.swift */; }; DBC7A672260C897100E57475 /* StatusContentWarningEditorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBC7A671260C897100E57475 /* StatusContentWarningEditorView.swift */; }; DBC7A67C260DFADE00E57475 /* StatusPublishService.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBC7A67B260DFADE00E57475 /* StatusPublishService.swift */; }; DBCBCBF4267CB070000F5B51 /* Decode85.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBCBCBF3267CB070000F5B51 /* Decode85.swift */; }; @@ -598,6 +608,13 @@ remoteGlobalIDString = DB89B9ED25C10FD0008580ED; remoteInfo = CoreDataStack; }; + DBC6461A26A170AB00B0E31B /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = DB427DCA25BAA00100D1B89D /* Project object */; + proxyType = 1; + remoteGlobalIDString = DBC6461126A170AB00B0E31B; + remoteInfo = ShareActionExtension; + }; DBF8AE18263293E400C9C23C /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = DB427DCA25BAA00100D1B89D /* Project object */; @@ -627,6 +644,7 @@ dstPath = ""; dstSubfolderSpec = 13; files = ( + DBC6461C26A170AB00B0E31B /* ShareActionExtension.appex in Embed App Extensions */, DBF8AE1A263293E400C9C23C /* NotificationService.appex in Embed App Extensions */, ); name = "Embed App Extensions"; @@ -1068,7 +1086,6 @@ DB9D7C20269824B80054B3DF /* APIService+Filter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Filter.swift"; sourceTree = ""; }; DB9E0D6E25EE008500CFDD76 /* UIInterpolatingMotionEffect.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIInterpolatingMotionEffect.swift; sourceTree = ""; }; DBA088DE26958164003EB4B2 /* UserFetchedResultsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserFetchedResultsController.swift; sourceTree = ""; }; - DBA0A10825FB3C2B0079C110 /* RoundedEdgesButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoundedEdgesButton.swift; sourceTree = ""; }; DBA0A11225FB3FC10079C110 /* ComposeToolbarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeToolbarView.swift; sourceTree = ""; }; DBA1DB7F268F84F80052DB59 /* NotificationType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationType.swift; sourceTree = ""; }; DBA465922696B495002B41DB /* APIService+WebFinger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+WebFinger.swift"; sourceTree = ""; }; @@ -1121,6 +1138,11 @@ DBBF1DC6265251D400E5B703 /* AutoCompleteViewModel+State.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AutoCompleteViewModel+State.swift"; sourceTree = ""; }; DBBF1DC82652538500E5B703 /* AutoCompleteSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoCompleteSection.swift; sourceTree = ""; }; DBBF1DCA2652539E00E5B703 /* AutoCompleteItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoCompleteItem.swift; sourceTree = ""; }; + DBC6461226A170AB00B0E31B /* ShareActionExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = ShareActionExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; + DBC6461426A170AB00B0E31B /* ShareViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareViewController.swift; sourceTree = ""; }; + DBC6461726A170AB00B0E31B /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/MainInterface.storyboard; sourceTree = ""; }; + DBC6461926A170AB00B0E31B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + DBC6462226A1712000B0E31B /* ShareViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ShareViewModel.swift; sourceTree = ""; }; DBC7A671260C897100E57475 /* StatusContentWarningEditorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusContentWarningEditorView.swift; sourceTree = ""; }; DBC7A67B260DFADE00E57475 /* StatusPublishService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusPublishService.swift; sourceTree = ""; }; DBCBCBF3267CB070000F5B51 /* Decode85.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Decode85.swift; sourceTree = ""; }; @@ -1190,6 +1212,7 @@ DB0140BD25C40D7500F9F3CF /* CommonOSLog in Frameworks */, DB03F7ED268976B5007B274C /* MetaTextView in Frameworks */, DB89BA0325C10FD0008580ED /* CoreDataStack.framework in Frameworks */, + DBC6462B26A1738900B0E31B /* MastodonUI in Frameworks */, 2D42FF6125C8177C004A627A /* ActiveLabel in Frameworks */, DB9A487E2603456B008B817C /* UITextView+Placeholder in Frameworks */, 2D939AC825EE14620076FA61 /* CropViewController in Frameworks */, @@ -1253,6 +1276,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + DBC6460F26A170AB00B0E31B /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + DBC6462526A1720B00B0E31B /* MastodonUI in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; DBF8AE10263293E400C9C23C /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -1458,7 +1489,6 @@ 2D42FF8425C8224F004A627A /* HitTestExpandedButton.swift */, DB118A8B25E4BFB500FAB162 /* HighlightDimmableButton.swift */, 0FAA101125E105390017CCDE /* PrimaryActionButton.swift */, - DBA0A10825FB3C2B0079C110 /* RoundedEdgesButton.swift */, ); path = Button; sourceTree = ""; @@ -1876,10 +1906,11 @@ DB427DD425BAA00100D1B89D /* Mastodon */, DB427DEB25BAA00100D1B89D /* MastodonTests */, DB427DF625BAA00100D1B89D /* MastodonUITests */, + DB6804802637CD4C00430867 /* AppShared */, DB89B9EF25C10FD0008580ED /* CoreDataStack */, DB89B9FC25C10FD0008580ED /* CoreDataStackTests */, DBF8AE14263293E400C9C23C /* NotificationService */, - DB6804802637CD4C00430867 /* AppShared */, + DBC6461326A170AB00B0E31B /* ShareActionExtension */, DB427DD325BAA00100D1B89D /* Products */, 1EBA4F56E920856A3FC84ACB /* Pods */, 3FE14AD363ED19AE7FF210A6 /* Frameworks */, @@ -1897,6 +1928,7 @@ DB89B9F625C10FD0008580ED /* CoreDataStackTests.xctest */, DBF8AE13263293E400C9C23C /* NotificationService.appex */, DB68047F2637CD4C00430867 /* AppShared.framework */, + DBC6461226A170AB00B0E31B /* ShareActionExtension.appex */, ); name = Products; sourceTree = ""; @@ -2682,6 +2714,17 @@ path = Cell; sourceTree = ""; }; + DBC6461326A170AB00B0E31B /* ShareActionExtension */ = { + isa = PBXGroup; + children = ( + DBC6462226A1712000B0E31B /* ShareViewModel.swift */, + DBC6461426A170AB00B0E31B /* ShareViewController.swift */, + DBC6461626A170AB00B0E31B /* MainInterface.storyboard */, + DBC6461926A170AB00B0E31B /* Info.plist */, + ); + path = ShareActionExtension; + sourceTree = ""; + }; DBCBCBFD2680ADBA000F5B51 /* AsyncHomeTimeline */ = { isa = PBXGroup; children = ( @@ -2831,6 +2874,7 @@ DBF8AE19263293E400C9C23C /* PBXTargetDependency */, DB6804852637CD4C00430867 /* PBXTargetDependency */, DB6804CA2637CE3000430867 /* PBXTargetDependency */, + DBC6461B26A170AB00B0E31B /* PBXTargetDependency */, ); name = Mastodon; packageProductDependencies = ( @@ -2852,6 +2896,7 @@ DB0E2D2D26833FF700865C3C /* NukeFLAnimatedImagePlugin */, DB03F7EA268976B5007B274C /* MastodonMeta */, DB03F7EC268976B5007B274C /* MetaTextView */, + DBC6462A26A1738900B0E31B /* MastodonUI */, ); productName = Mastodon; productReference = DB427DD225BAA00100D1B89D /* Mastodon.app */; @@ -2956,6 +3001,26 @@ productReference = DB89B9F625C10FD0008580ED /* CoreDataStackTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; + DBC6461126A170AB00B0E31B /* ShareActionExtension */ = { + isa = PBXNativeTarget; + buildConfigurationList = DBC6462126A170AB00B0E31B /* Build configuration list for PBXNativeTarget "ShareActionExtension" */; + buildPhases = ( + DBC6460E26A170AB00B0E31B /* Sources */, + DBC6460F26A170AB00B0E31B /* Frameworks */, + DBC6461026A170AB00B0E31B /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = ShareActionExtension; + packageProductDependencies = ( + DBC6462426A1720B00B0E31B /* MastodonUI */, + ); + productName = ShareActionExtension; + productReference = DBC6461226A170AB00B0E31B /* ShareActionExtension.appex */; + productType = "com.apple.product-type.app-extension"; + }; DBF8AE12263293E400C9C23C /* NotificationService */ = { isa = PBXNativeTarget; buildConfigurationList = DBF8AE1E263293E400C9C23C /* Build configuration list for PBXNativeTarget "NotificationService" */; @@ -2985,7 +3050,7 @@ DB427DCA25BAA00100D1B89D /* Project object */ = { isa = PBXProject; attributes = { - LastSwiftUpdateCheck = 1240; + LastSwiftUpdateCheck = 1250; LastUpgradeCheck = 1240; TargetAttributes = { DB427DD125BAA00100D1B89D = { @@ -3012,6 +3077,9 @@ CreatedOnToolsVersion = 12.4; TestTargetID = DB427DD125BAA00100D1B89D; }; + DBC6461126A170AB00B0E31B = { + CreatedOnToolsVersion = 12.5.1; + }; DBF8AE12263293E400C9C23C = { CreatedOnToolsVersion = 12.4; }; @@ -3053,10 +3121,11 @@ DB427DD125BAA00100D1B89D /* Mastodon */, DB427DE725BAA00100D1B89D /* MastodonTests */, DB427DF225BAA00100D1B89D /* MastodonUITests */, + DB68047E2637CD4C00430867 /* AppShared */, DB89B9ED25C10FD0008580ED /* CoreDataStack */, DB89B9F525C10FD0008580ED /* CoreDataStackTests */, DBF8AE12263293E400C9C23C /* NotificationService */, - DB68047E2637CD4C00430867 /* AppShared */, + DBC6461126A170AB00B0E31B /* ShareActionExtension */, ); }; /* End PBXProject section */ @@ -3113,6 +3182,17 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + DBC6461026A170AB00B0E31B /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + DBC6461826A170AB00B0E31B /* MainInterface.storyboard in Resources */, + DBC6462726A1736000B0E31B /* Localizable.strings in Resources */, + DBC6462826A1736300B0E31B /* Assets.xcassets in Resources */, + DBC6462626A1736000B0E31B /* Localizable.stringsdict in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; DBF8AE11263293E400C9C23C /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -3364,7 +3444,6 @@ 5DDDF1932617442700311060 /* Mastodon+Entity+Account.swift in Sources */, DBAE3F882615DDF4004B8251 /* UserProviderFacade.swift in Sources */, 2D607AD826242FC500B70763 /* NotificationViewModel.swift in Sources */, - DBA0A10925FB3C2B0079C110 /* RoundedEdgesButton.swift in Sources */, DBABE3EC25ECAC4B00879EE5 /* WelcomeIllustrationView.swift in Sources */, DB564BD3269F3B35001E39A7 /* StatusFilterService.swift in Sources */, DB71FD4625F8C6D200512AE1 /* StatusProvider+UITableViewDataSourcePrefetching.swift in Sources */, @@ -3798,6 +3877,17 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + DBC6460E26A170AB00B0E31B /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + DBC6462326A1712000B0E31B /* ShareViewModel.swift in Sources */, + DBC6462926A1736700B0E31B /* Strings.swift in Sources */, + DBC6462C26A176B000B0E31B /* Assets.swift in Sources */, + DBC6461526A170AB00B0E31B /* ShareViewController.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; DBF8AE0F263293E400C9C23C /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -3860,6 +3950,11 @@ target = DB89B9ED25C10FD0008580ED /* CoreDataStack */; targetProxy = DB89BA0125C10FD0008580ED /* PBXContainerItemProxy */; }; + DBC6461B26A170AB00B0E31B /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = DBC6461126A170AB00B0E31B /* ShareActionExtension */; + targetProxy = DBC6461A26A170AB00B0E31B /* PBXContainerItemProxy */; + }; DBF8AE19263293E400C9C23C /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = DBF8AE12263293E400C9C23C /* NotificationService */; @@ -3911,6 +4006,14 @@ name = Localizable.stringsdict; sourceTree = ""; }; + DBC6461626A170AB00B0E31B /* MainInterface.storyboard */ = { + isa = PBXVariantGroup; + children = ( + DBC6461726A170AB00B0E31B /* Base */, + ); + name = MainInterface.storyboard; + sourceTree = ""; + }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ @@ -4036,6 +4139,7 @@ isa = XCBuildConfiguration; baseConfigurationReference = 2E1F6A67FDF9771D3E064FDC /* Pods-Mastodon.debug.xcconfig */; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CLANG_ENABLE_MODULES = YES; @@ -4063,6 +4167,7 @@ isa = XCBuildConfiguration; baseConfigurationReference = 75E3471C898DDD9631729B6E /* Pods-Mastodon.release.xcconfig */; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CLANG_ENABLE_MODULES = YES; @@ -4325,6 +4430,86 @@ }; name = Release; }; + DBC6461D26A170AB00B0E31B /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 5Z4GVSS33P; + INFOPLIST_FILE = ShareActionExtension/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 14.5; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app.ShareActionExtension; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + DBC6461E26A170AB00B0E31B /* ASDK - Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 5Z4GVSS33P; + INFOPLIST_FILE = ShareActionExtension/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 14.5; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app.ShareActionExtension; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = "ASDK - Debug"; + }; + DBC6461F26A170AB00B0E31B /* ASDK - Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 5Z4GVSS33P; + INFOPLIST_FILE = ShareActionExtension/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 14.5; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app.ShareActionExtension; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = "ASDK - Release"; + }; + DBC6462026A170AB00B0E31B /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 5Z4GVSS33P; + INFOPLIST_FILE = ShareActionExtension/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 14.5; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app.ShareActionExtension; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; DBCBCC0E2680BE3E000F5B51 /* ASDK - Release */ = { isa = XCBuildConfiguration; buildSettings = { @@ -4391,6 +4576,7 @@ isa = XCBuildConfiguration; baseConfigurationReference = BD7598A87F4497045EDEF252 /* Pods-Mastodon.asdk - release.xcconfig */; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CLANG_ENABLE_MODULES = YES; @@ -4624,6 +4810,7 @@ isa = XCBuildConfiguration; baseConfigurationReference = 1D6D967E77A5357E2C6110D9 /* Pods-Mastodon.asdk - debug.xcconfig */; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CLANG_ENABLE_MODULES = YES; @@ -4917,6 +5104,17 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + DBC6462126A170AB00B0E31B /* Build configuration list for PBXNativeTarget "ShareActionExtension" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + DBC6461D26A170AB00B0E31B /* Debug */, + DBC6461E26A170AB00B0E31B /* ASDK - Debug */, + DBC6461F26A170AB00B0E31B /* ASDK - Release */, + DBC6462026A170AB00B0E31B /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; DBF8AE1E263293E400C9C23C /* Build configuration list for PBXNativeTarget "NotificationService" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -5169,6 +5367,14 @@ package = DBB525062611EAC0002F1F29 /* XCRemoteSwiftPackageReference "Tabman" */; productName = Tabman; }; + DBC6462426A1720B00B0E31B /* MastodonUI */ = { + isa = XCSwiftPackageProductDependency; + productName = MastodonUI; + }; + DBC6462A26A1738900B0E31B /* MastodonUI */ = { + isa = XCSwiftPackageProductDependency; + productName = MastodonUI; + }; DBF7A0FB26830C33004176A2 /* FPSIndicator */ = { isa = XCSwiftPackageProductDependency; package = DBF7A0FA26830C33004176A2 /* XCRemoteSwiftPackageReference "FPSIndicator" */; diff --git a/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist b/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist index ecde1bcc..4c04247e 100644 --- a/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist @@ -12,7 +12,7 @@ CoreDataStack.xcscheme_^#shared#^_ orderHint - 20 + 21 Mastodon - ASDK.xcscheme_^#shared#^_ @@ -37,7 +37,12 @@ NotificationService.xcscheme_^#shared#^_ orderHint - 21 + 22 + + ShareActionExtension.xcscheme_^#shared#^_ + + orderHint + 30 SuppressBuildableAutocreation diff --git a/Mastodon/Extension/UIImage.swift b/Mastodon/Extension/UIImage.swift index 35766c0b..7054661b 100644 --- a/Mastodon/Extension/UIImage.swift +++ b/Mastodon/Extension/UIImage.swift @@ -9,17 +9,6 @@ import CoreImage import CoreImage.CIFilterBuiltins import UIKit -extension UIImage { - static func placeholder(size: CGSize = CGSize(width: 1, height: 1), color: UIColor) -> UIImage { - let render = UIGraphicsImageRenderer(size: size) - - return render.image { (context: UIGraphicsImageRendererContext) in - context.cgContext.setFillColor(color.cgColor) - context.fill(CGRect(origin: .zero, size: size)) - } - } -} - // refs: https://www.hackingwithswift.com/example-code/media/how-to-read-the-average-color-of-a-uiimage-using-ciareaaverage extension UIImage { @available(iOS 14.0, *) diff --git a/Mastodon/Scene/Compose/ComposeViewController.swift b/Mastodon/Scene/Compose/ComposeViewController.swift index ca0b3c44..b48b01a7 100644 --- a/Mastodon/Scene/Compose/ComposeViewController.swift +++ b/Mastodon/Scene/Compose/ComposeViewController.swift @@ -14,6 +14,7 @@ import MetaTextView import MastodonMeta import Meta import Nuke +import MastodonUI final class ComposeViewController: UIViewController, NeedsDependency { diff --git a/Mastodon/Scene/HomeTimeline/View/HomeTimelineNavigationBarTitleView.swift b/Mastodon/Scene/HomeTimeline/View/HomeTimelineNavigationBarTitleView.swift index 84a46367..caa2fbe0 100644 --- a/Mastodon/Scene/HomeTimeline/View/HomeTimelineNavigationBarTitleView.swift +++ b/Mastodon/Scene/HomeTimeline/View/HomeTimelineNavigationBarTitleView.swift @@ -7,6 +7,7 @@ import os.log import UIKit +import MastodonUI protocol HomeTimelineNavigationBarTitleViewDelegate: AnyObject { func homeTimelineNavigationBarTitleView(_ titleView: HomeTimelineNavigationBarTitleView, logoButtonDidPressed sender: UIButton) diff --git a/Mastodon/Scene/Profile/Header/View/ProfileRelationshipActionButton.swift b/Mastodon/Scene/Profile/Header/View/ProfileRelationshipActionButton.swift index 948d22b0..aed53904 100644 --- a/Mastodon/Scene/Profile/Header/View/ProfileRelationshipActionButton.swift +++ b/Mastodon/Scene/Profile/Header/View/ProfileRelationshipActionButton.swift @@ -6,6 +6,7 @@ // import UIKit +import MastodonUI final class ProfileRelationshipActionButton: RoundedEdgesButton { diff --git a/MastodonSDK/Package.swift b/MastodonSDK/Package.swift index 47589f32..23b5015f 100644 --- a/MastodonSDK/Package.swift +++ b/MastodonSDK/Package.swift @@ -12,6 +12,12 @@ let package = Package( .library( name: "MastodonSDK", targets: ["MastodonSDK"]), + .library( + name: "MastodonUI", + targets: ["MastodonUI"]), + .library( + name: "MastodonExtension", + targets: ["MastodonExtension"]), ], dependencies: [ .package(url: "https://github.com/SwiftyJSON/SwiftyJSON.git", from: "5.0.0"), @@ -27,6 +33,14 @@ let package = Package( .product(name: "NIOHTTP1", package: "swift-nio"), ] ), + .target( + name: "MastodonUI", + dependencies: ["MastodonExtension"] + ), + .target( + name: "MastodonExtension", + dependencies: [] + ), .testTarget( name: "MastodonSDKTests", dependencies: ["MastodonSDK"] diff --git a/MastodonSDK/Sources/MastodonExtension/UIImage.swift b/MastodonSDK/Sources/MastodonExtension/UIImage.swift new file mode 100644 index 00000000..79896fda --- /dev/null +++ b/MastodonSDK/Sources/MastodonExtension/UIImage.swift @@ -0,0 +1,19 @@ +// +// UIImage.swift +// +// +// Created by MainasuK Cirno on 2021-7-16. +// + +import UIKit + +extension UIImage { + public static func placeholder(size: CGSize = CGSize(width: 1, height: 1), color: UIColor) -> UIImage { + let render = UIGraphicsImageRenderer(size: size) + + return render.image { (context: UIGraphicsImageRendererContext) in + context.cgContext.setFillColor(color.cgColor) + context.fill(CGRect(origin: .zero, size: size)) + } + } +} diff --git a/Mastodon/Scene/Share/View/Button/RoundedEdgesButton.swift b/MastodonSDK/Sources/MastodonUI/RoundedEdgesButton.swift similarity index 71% rename from Mastodon/Scene/Share/View/Button/RoundedEdgesButton.swift rename to MastodonSDK/Sources/MastodonUI/RoundedEdgesButton.swift index 266fa659..4d62a5c2 100644 --- a/Mastodon/Scene/Share/View/Button/RoundedEdgesButton.swift +++ b/MastodonSDK/Sources/MastodonUI/RoundedEdgesButton.swift @@ -1,15 +1,15 @@ // // RoundedEdgesButton.swift -// Mastodon +// MastodonUI // // Created by MainasuK Cirno on 2021-3-12. // import UIKit -class RoundedEdgesButton: UIButton { +open class RoundedEdgesButton: UIButton { - override func layoutSubviews() { + open override func layoutSubviews() { super.layoutSubviews() layer.masksToBounds = true diff --git a/MastodonSDK/Sources/MastodonUI/import.swift b/MastodonSDK/Sources/MastodonUI/import.swift new file mode 100644 index 00000000..a96291dd --- /dev/null +++ b/MastodonSDK/Sources/MastodonUI/import.swift @@ -0,0 +1 @@ +@_exported import MastodonExtension diff --git a/ShareActionExtension/Base.lproj/MainInterface.storyboard b/ShareActionExtension/Base.lproj/MainInterface.storyboard new file mode 100644 index 00000000..e971d077 --- /dev/null +++ b/ShareActionExtension/Base.lproj/MainInterface.storyboard @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ShareActionExtension/Info.plist b/ShareActionExtension/Info.plist new file mode 100644 index 00000000..ea226b26 --- /dev/null +++ b/ShareActionExtension/Info.plist @@ -0,0 +1,45 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + ShareActionExtension + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + NSExtension + + NSExtensionAttributes + + NSExtensionActivationRule + + NSExtensionActivationSupportsImageWithMaxCount + 4 + NSExtensionActivationSupportsMovieWithMaxCount + 1 + NSExtensionActivationSupportsText + + NSExtensionActivationSupportsWebURLWithMaxCount + 1 + + + NSExtensionMainStoryboard + MainInterface + NSExtensionPointIdentifier + com.apple.share-services + + + diff --git a/ShareActionExtension/ShareViewController.swift b/ShareActionExtension/ShareViewController.swift new file mode 100644 index 00000000..c0479c96 --- /dev/null +++ b/ShareActionExtension/ShareViewController.swift @@ -0,0 +1,102 @@ +// +// ShareViewController.swift +// MastodonShareAction +// +// Created by MainasuK Cirno on 2021-7-16. +// + +import os.log +import UIKit +import Combine +import MastodonUI + +class ShareViewController: UIViewController { + + let logger = Logger(subsystem: "ShareViewController", category: "UI") + + var disposeBag = Set() + let viewModel = ShareViewModel() + + let publishButton: UIButton = { + let button = RoundedEdgesButton(type: .custom) + button.setTitle(L10n.Scene.Compose.composeAction, for: .normal) + button.titleLabel?.font = .systemFont(ofSize: 14, weight: .bold) + button.setBackgroundImage(.placeholder(color: Asset.Colors.brandBlue.color), for: .normal) + button.setBackgroundImage(.placeholder(color: Asset.Colors.brandBlue.color.withAlphaComponent(0.5)), for: .highlighted) + button.setBackgroundImage(.placeholder(color: Asset.Colors.Button.disabled.color), for: .disabled) + button.setTitleColor(.white, for: .normal) + button.contentEdgeInsets = UIEdgeInsets(top: 6, left: 16, bottom: 5, right: 16) // set 28pt height + button.adjustsImageWhenHighlighted = false + return button + }() + + private(set) lazy var cancelBarButtonItem = UIBarButtonItem(title: L10n.Common.Controls.Actions.cancel, style: .plain, target: self, action: #selector(ShareViewController.cancelBarButtonItemPressed(_:))) + private(set) lazy var publishBarButtonItem: UIBarButtonItem = { + let barButtonItem = UIBarButtonItem(customView: publishButton) + barButtonItem.target = self + barButtonItem.action = #selector(ShareViewController.publishBarButtonItemPressed(_:)) + return barButtonItem + }() + + let activityIndicatorBarButtonItem: UIBarButtonItem = { + let indicatorView = UIActivityIndicatorView(style: .medium) + let barButtonItem = UIBarButtonItem(customView: indicatorView) + indicatorView.startAnimating() + return barButtonItem + }() + +// let tableView: ComposeTableView = { +// let tableView = ComposeTableView() +// tableView.register(ComposeStatusContentTableViewCell.self, forCellReuseIdentifier: String(describing: ComposeStatusContentTableViewCell.self)) +// tableView.register(ComposeStatusAttachmentTableViewCell.self, forCellReuseIdentifier: String(describing: ComposeStatusAttachmentTableViewCell.self)) +// tableView.alwaysBounceVertical = true +// tableView.separatorStyle = .none +// tableView.tableFooterView = UIView() +// return tableView +// }() + +} + + +extension ShareViewController { + + override func viewDidLoad() { + super.viewDidLoad() + + view.backgroundColor = Asset.Colors.Background.systemBackground.color + + navigationItem.leftBarButtonItem = cancelBarButtonItem + viewModel.isBusy + .receive(on: DispatchQueue.main) + .sink { [weak self] isBusy in + guard let self = self else { return } + self.navigationItem.rightBarButtonItem = isBusy ? self.activityIndicatorBarButtonItem : self.publishBarButtonItem + } + .store(in: &disposeBag) + +// viewModel.authentication +// .receive(on: DispatchQueue.main) +// .sink { [weak self] result in +// guard let self = self else { return } +// } + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + viewModel.viewDidAppear.value = true +// extensionContext + } + +} + +extension ShareViewController { + @objc private func cancelBarButtonItemPressed(_ sender: UIBarButtonItem) { + logger.debug("\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public)") + extensionContext?.cancelRequest(withError: ShareViewModel.ShareError.userCancelShare) + } + + @objc private func publishBarButtonItemPressed(_ sender: UIBarButtonItem) { + logger.debug("\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public)") + } +} diff --git a/ShareActionExtension/ShareViewModel.swift b/ShareActionExtension/ShareViewModel.swift new file mode 100644 index 00000000..bea00c51 --- /dev/null +++ b/ShareActionExtension/ShareViewModel.swift @@ -0,0 +1,99 @@ +// +// ShareViewModel.swift +// MastodonShareAction +// +// Created by MainasuK Cirno on 2021-7-16. +// + +import os.log +import Foundation +import Combine +import CoreData +import CoreDataStack + +final class ShareViewModel { + + let logger = Logger(subsystem: "ShareViewModel", category: "logic") + + var disposeBag = Set() + + // input + let viewDidAppear = CurrentValueSubject(false) + private var coreDataStack: CoreDataStack? + var managedObjectContext: NSManagedObjectContext? + + // output + let authentication = CurrentValueSubject?, Never>(nil) + let isFetchAuthentication = CurrentValueSubject(true) + let isBusy = CurrentValueSubject(true) + let isValid = CurrentValueSubject(false) + + init() { + viewDidAppear.receive(on: DispatchQueue.main) + .removeDuplicates() + .sink { [weak self] viewDidAppear in + guard let self = self else { return } + guard viewDidAppear else { return } + self.setupCoreData() + } + .store(in: &disposeBag) + + authentication + .map { result in result == nil } + .assign(to: \.value, on: isFetchAuthentication) + .store(in: &disposeBag) + + isFetchAuthentication + .receive(on: DispatchQueue.main) + .assign(to: \.value, on: isBusy) + .store(in: &disposeBag) + } + +} + +extension ShareViewModel { + enum ShareError: Error { + case `internal`(error: Error) + case userCancelShare + case missingAuthentication + } +} + +extension ShareViewModel { + private func setupCoreData() { + logger.debug("\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public)") + DispatchQueue.global().async { + let _coreDataStack = CoreDataStack() + self.coreDataStack = _coreDataStack + self.managedObjectContext = _coreDataStack.persistentContainer.viewContext + + _coreDataStack.didFinishLoad + .receive(on: RunLoop.main) + .sink { [weak self] didFinishLoad in + guard let self = self else { return } + guard didFinishLoad else { return } + guard let managedObjectContext = self.managedObjectContext else { return } + + self.logger.debug("\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): fetch authentication…") + managedObjectContext.perform { + do { + let request = MastodonAuthentication.sortedFetchRequest + let authentications = try managedObjectContext.fetch(request) + let authentication = authentications.sorted(by: { $0.activedAt > $1.activedAt }).first + guard let activeAuthentication = authentication else { + self.authentication.value = .failure(ShareError.missingAuthentication) + return + } + self.authentication.value = .success(activeAuthentication) + self.logger.debug("\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): fetch authentication success \(activeAuthentication.userID)") + } catch { + self.authentication.value = .failure(ShareError.internal(error: error)) + self.logger.debug("\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): fetch authentication fail \(error.localizedDescription)") + assertionFailure(error.localizedDescription) + } + } + } + .store(in: &self.disposeBag) + } + } +} From 079e611f330d092d2c8ef0de74d4c4d1325334a4 Mon Sep 17 00:00:00 2001 From: CMK Date: Fri, 16 Jul 2021 21:21:18 +0800 Subject: [PATCH 02/11] feat: add compose view --- Mastodon.xcodeproj/project.pbxproj | 42 +++++++++- .../xcschemes/xcschememanagement.plist | 14 ++-- .../xcshareddata/swiftpm/Package.resolved | 8 +- ...meTimelineViewController+DebugAction.swift | 19 ++++- MastodonSDK/Package.swift | 10 ++- .../Sources/MastodonUI/AnimatedImage.swift | 56 +++++++++++++ .../MastodonUI/Compose/ComposeView.swift | 68 ++++++++++++++++ .../MastodonUI/Compose/ComposeViewModel.swift | 46 +++++++++++ .../MastodonUI/Compose/StatusAuthorView.swift | 44 ++++++++++ .../MastodonUI/Compose/TextEditorView.swift | 80 +++++++++++++++++++ ShareActionExtension/Info.plist | 26 +++--- .../ShareViewController.swift | 27 ++++--- ShareActionExtension/ShareViewModel.swift | 2 + 13 files changed, 399 insertions(+), 43 deletions(-) create mode 100644 MastodonSDK/Sources/MastodonUI/AnimatedImage.swift create mode 100644 MastodonSDK/Sources/MastodonUI/Compose/ComposeView.swift create mode 100644 MastodonSDK/Sources/MastodonUI/Compose/ComposeViewModel.swift create mode 100644 MastodonSDK/Sources/MastodonUI/Compose/StatusAuthorView.swift create mode 100644 MastodonSDK/Sources/MastodonUI/Compose/TextEditorView.swift diff --git a/Mastodon.xcodeproj/project.pbxproj b/Mastodon.xcodeproj/project.pbxproj index 423886e1..ae5393fe 100644 --- a/Mastodon.xcodeproj/project.pbxproj +++ b/Mastodon.xcodeproj/project.pbxproj @@ -491,6 +491,8 @@ DBC6462926A1736700B0E31B /* Strings.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB98338525C945ED00AD9700 /* Strings.swift */; }; DBC6462B26A1738900B0E31B /* MastodonUI in Frameworks */ = {isa = PBXBuildFile; productRef = DBC6462A26A1738900B0E31B /* MastodonUI */; }; DBC6462C26A176B000B0E31B /* Assets.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB98338625C945ED00AD9700 /* Assets.swift */; }; + DBC6463326A195DB00B0E31B /* AppShared.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DB68047F2637CD4C00430867 /* AppShared.framework */; }; + DBC6463726A195DB00B0E31B /* CoreDataStack.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DB89B9EE25C10FD0008580ED /* CoreDataStack.framework */; }; DBC7A672260C897100E57475 /* StatusContentWarningEditorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBC7A671260C897100E57475 /* StatusContentWarningEditorView.swift */; }; DBC7A67C260DFADE00E57475 /* StatusPublishService.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBC7A67B260DFADE00E57475 /* StatusPublishService.swift */; }; DBCBCBF4267CB070000F5B51 /* Decode85.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBCBCBF3267CB070000F5B51 /* Decode85.swift */; }; @@ -615,6 +617,20 @@ remoteGlobalIDString = DBC6461126A170AB00B0E31B; remoteInfo = ShareActionExtension; }; + DBC6463526A195DB00B0E31B /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = DB427DCA25BAA00100D1B89D /* Project object */; + proxyType = 1; + remoteGlobalIDString = DB68047E2637CD4C00430867; + remoteInfo = AppShared; + }; + DBC6463926A195DB00B0E31B /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = DB427DCA25BAA00100D1B89D /* Project object */; + proxyType = 1; + remoteGlobalIDString = DB89B9ED25C10FD0008580ED; + remoteInfo = CoreDataStack; + }; DBF8AE18263293E400C9C23C /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = DB427DCA25BAA00100D1B89D /* Project object */; @@ -1281,6 +1297,8 @@ buildActionMask = 2147483647; files = ( DBC6462526A1720B00B0E31B /* MastodonUI in Frameworks */, + DBC6463726A195DB00B0E31B /* CoreDataStack.framework in Frameworks */, + DBC6463326A195DB00B0E31B /* AppShared.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -3012,6 +3030,8 @@ buildRules = ( ); dependencies = ( + DBC6463626A195DB00B0E31B /* PBXTargetDependency */, + DBC6463A26A195DB00B0E31B /* PBXTargetDependency */, ); name = ShareActionExtension; packageProductDependencies = ( @@ -3955,6 +3975,16 @@ target = DBC6461126A170AB00B0E31B /* ShareActionExtension */; targetProxy = DBC6461A26A170AB00B0E31B /* PBXContainerItemProxy */; }; + DBC6463626A195DB00B0E31B /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = DB68047E2637CD4C00430867 /* AppShared */; + targetProxy = DBC6463526A195DB00B0E31B /* PBXContainerItemProxy */; + }; + DBC6463A26A195DB00B0E31B /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = DB89B9ED25C10FD0008580ED /* CoreDataStack */; + targetProxy = DBC6463926A195DB00B0E31B /* PBXContainerItemProxy */; + }; DBF8AE19263293E400C9C23C /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = DBF8AE12263293E400C9C23C /* NotificationService */; @@ -4434,14 +4464,15 @@ isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 40; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = ShareActionExtension/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 14.5; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); + MARKETING_VERSION = 0.9.0; PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app.ShareActionExtension; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; @@ -4454,14 +4485,15 @@ isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 40; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = ShareActionExtension/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 14.5; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); + MARKETING_VERSION = 0.9.0; PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app.ShareActionExtension; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; @@ -4474,14 +4506,15 @@ isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 40; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = ShareActionExtension/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 14.5; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); + MARKETING_VERSION = 0.9.0; PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app.ShareActionExtension; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; @@ -4494,14 +4527,15 @@ isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 40; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = ShareActionExtension/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 14.5; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); + MARKETING_VERSION = 0.9.0; PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app.ShareActionExtension; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; diff --git a/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist b/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist index 4c04247e..e417a0d4 100644 --- a/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist @@ -12,37 +12,37 @@ CoreDataStack.xcscheme_^#shared#^_ orderHint - 21 + 20 Mastodon - ASDK.xcscheme_^#shared#^_ orderHint - 2 + 5 Mastodon - RTL.xcscheme_^#shared#^_ orderHint - 3 + 7 Mastodon - Release.xcscheme_^#shared#^_ orderHint - 1 + 3 Mastodon.xcscheme_^#shared#^_ orderHint - 0 + 1 NotificationService.xcscheme_^#shared#^_ orderHint - 22 + 21 ShareActionExtension.xcscheme_^#shared#^_ orderHint - 30 + 22 SuppressBuildableAutocreation diff --git a/Mastodon.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Mastodon.xcworkspace/xcshareddata/swiftpm/Package.resolved index edf6e564..78b4a755 100644 --- a/Mastodon.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Mastodon.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -105,8 +105,8 @@ "repositoryURL": "https://github.com/onevcat/Kingfisher.git", "state": { "branch": null, - "revision": "15d199e84677303a7004ed2c5ecaa1a90f3863f8", - "version": "6.2.1" + "revision": "44450a8f564d7c0165f736ba2250649ff8d3e556", + "version": "6.3.0" } }, { @@ -123,8 +123,8 @@ "repositoryURL": "https://github.com/kean/Nuke.git", "state": { "branch": null, - "revision": "69ae6d5b8c4b898450432f94bd35f863d3830cfc", - "version": "10.3.0" + "revision": "83e1edaa5a30c567eb129c21c6d00f2f552d2c6f", + "version": "10.3.1" } }, { diff --git a/Mastodon/Scene/HomeTimeline/HomeTimelineViewController+DebugAction.swift b/Mastodon/Scene/HomeTimeline/HomeTimelineViewController+DebugAction.swift index 7c1f79db..0b439753 100644 --- a/Mastodon/Scene/HomeTimeline/HomeTimelineViewController+DebugAction.swift +++ b/Mastodon/Scene/HomeTimeline/HomeTimelineViewController+DebugAction.swift @@ -5,13 +5,15 @@ // Created by MainasuK Cirno on 2021-2-5. // + +#if DEBUG import os.log import UIKit import CoreData import CoreDataStack - -#if DEBUG import FLEX +import SwiftUI +import MastodonUI extension HomeTimelineViewController { var debugMenu: UIMenu { @@ -55,6 +57,10 @@ extension HomeTimelineViewController { guard let self = self else { return } self.showThreadAction(action) }, + UIAction(title: "Show Share Action Compose", image: UIImage(systemName: "square.and.arrow.up"), attributes: []) { [weak self] action in + guard let self = self else { return } + self.showShareActionExtensionComposeView(action) + }, UIAction(title: "Settings", image: UIImage(systemName: "gear"), attributes: []) { [weak self] action in guard let self = self else { return } self.showSettings(action) @@ -363,5 +369,14 @@ extension HomeTimelineViewController { transition: .modal(animated: true, completion: nil) ) } + + @objc private func showShareActionExtensionComposeView(_ sender: UIAction) { + let viewController = UIHostingController( + rootView: ComposeView().environmentObject(MastodonUI.ComposeViewModel()) + ) + let navigationController = UINavigationController(rootViewController: viewController) + present(navigationController, animated: true, completion: nil) + } + } #endif diff --git a/MastodonSDK/Package.swift b/MastodonSDK/Package.swift index 23b5015f..4b3ba9d4 100644 --- a/MastodonSDK/Package.swift +++ b/MastodonSDK/Package.swift @@ -6,7 +6,7 @@ import PackageDescription let package = Package( name: "MastodonSDK", platforms: [ - .iOS(.v13), + .iOS(.v14), ], products: [ .library( @@ -22,6 +22,8 @@ let package = Package( dependencies: [ .package(url: "https://github.com/SwiftyJSON/SwiftyJSON.git", from: "5.0.0"), .package(url: "https://github.com/apple/swift-nio.git", from: "1.0.0"), + .package(url: "https://github.com/kean/Nuke.git", from: "10.3.1"), + .package(name: "NukeFLAnimatedImagePlugin", url: "https://github.com/kean/Nuke-FLAnimatedImage-Plugin.git", from: "8.0.0"), ], targets: [ // Targets are the basic building blocks of a package. A target can define a module or a test suite. @@ -35,7 +37,11 @@ let package = Package( ), .target( name: "MastodonUI", - dependencies: ["MastodonExtension"] + dependencies: [ + "MastodonExtension", + "Nuke", + "NukeFLAnimatedImagePlugin" + ] ), .target( name: "MastodonExtension", diff --git a/MastodonSDK/Sources/MastodonUI/AnimatedImage.swift b/MastodonSDK/Sources/MastodonUI/AnimatedImage.swift new file mode 100644 index 00000000..d6c91678 --- /dev/null +++ b/MastodonSDK/Sources/MastodonUI/AnimatedImage.swift @@ -0,0 +1,56 @@ +// +// AnimatedImage.swift +// +// +// Created by MainasuK Cirno on 2021-7-16. +// + +import SwiftUI +import Nuke +import FLAnimatedImage + +struct AnimatedImage: UIViewRepresentable { + + let imageURL: URL? + + func makeUIView(context: Context) -> FLAnimatedImageViewProxy { + let proxy = FLAnimatedImageViewProxy(frame: .zero) + Nuke.loadImage(with: imageURL, into: proxy.imageView) + return proxy + } + + func updateUIView(_ proxy: FLAnimatedImageViewProxy, context: Context) { + Nuke.cancelRequest(for: proxy.imageView) + Nuke.loadImage(with: imageURL, into: proxy.imageView) + } +} + +final class FLAnimatedImageViewProxy: UIView { + let imageView = FLAnimatedImageView() + + override init(frame: CGRect) { + super.init(frame: frame) + + imageView.translatesAutoresizingMaskIntoConstraints = false + addSubview(imageView) + NSLayoutConstraint.activate([ + imageView.topAnchor.constraint(equalTo: topAnchor), + imageView.leadingAnchor.constraint(equalTo: leadingAnchor), + imageView.trailingAnchor.constraint(equalTo: trailingAnchor), + imageView.bottomAnchor.constraint(equalTo: bottomAnchor), + ]) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +struct AnimatedImage_Previews: PreviewProvider { + static var previews: some View { + AnimatedImage( + imageURL: URL(string: "https://upload.wikimedia.org/wikipedia/commons/2/2c/Rotating_earth_%28large%29.gif") + ) + .frame(width: 300, height: 300) + } +} diff --git a/MastodonSDK/Sources/MastodonUI/Compose/ComposeView.swift b/MastodonSDK/Sources/MastodonUI/Compose/ComposeView.swift new file mode 100644 index 00000000..10ae383d --- /dev/null +++ b/MastodonSDK/Sources/MastodonUI/Compose/ComposeView.swift @@ -0,0 +1,68 @@ +// +// ComposeView.swift +// +// +// Created by MainasuK Cirno on 2021-7-16. +// + +import SwiftUI + +public struct ComposeView: View { + + @EnvironmentObject public var viewModel: ComposeViewModel + + public init() { } + + public var body: some View { + GeometryReader { proxy in + ScrollView(.vertical) { + StatusAuthorView( + avatarImageURL: viewModel.avatarImageURL, + name: viewModel.authorName, + username: viewModel.authorUsername + ) + TextEditorView( + string: $viewModel.statusContent, + width: viewModel.frame.width, + attributedString: viewModel.statusContentAttributedString + ) + .frame(width: viewModel.frame.width) + .frame(minHeight: 100) + ForEach(viewModel.attachments, id: \.self) { image in + Image(uiImage: image) + .resizable() + .aspectRatio(16.0/9.0, contentMode: .fill) + .frame(maxWidth: .infinity) + .background(Color.gray) + .cornerRadius(4) + } + } // end ScrollView + .preference( + key: ComposeViewFramePreferenceKey.self, + value: proxy.frame(in: .local) + ) + .onPreferenceChange(ComposeViewFramePreferenceKey.self) { frame in + viewModel.frame = frame + print(frame) + } + } + } +} + +struct ComposeViewFramePreferenceKey: PreferenceKey { + static var defaultValue: CGRect = .zero + static func reduce(value: inout CGRect, nextValue: () -> CGRect) { } +} + +struct ComposeView_Previews: PreviewProvider { + + static let viewModel: ComposeViewModel = { + let viewModel = ComposeViewModel() + return viewModel + }() + + static var previews: some View { + ComposeView().environmentObject(viewModel) + } + +} diff --git a/MastodonSDK/Sources/MastodonUI/Compose/ComposeViewModel.swift b/MastodonSDK/Sources/MastodonUI/Compose/ComposeViewModel.swift new file mode 100644 index 00000000..4679c61c --- /dev/null +++ b/MastodonSDK/Sources/MastodonUI/Compose/ComposeViewModel.swift @@ -0,0 +1,46 @@ +// +// ComposeViewModel.swift +// ShareActionExtension +// +// Created by MainasuK Cirno on 2021-7-16. +// + +import Foundation +import SwiftUI +import Combine + +public class ComposeViewModel: ObservableObject { + + var disposeBag = Set() + + @Published var frame: CGRect = .zero + + @Published var avatarImageURL: URL? + @Published var authorName: String = "" + @Published var authorUsername: String = "" + + @Published var statusContent = "" + @Published var statusContentAttributedString = NSAttributedString() + @Published var contentWarningContent = "" + + @Published var attachments: [UIImage] = [] + + public init() { + $statusContent + .map { NSAttributedString(string: $0) } + .assign(to: &$statusContentAttributedString) + + #if DEBUG + avatarImageURL = URL(string: "https://upload.wikimedia.org/wikipedia/commons/2/2c/Rotating_earth_%28large%29.gif") + authorName = "Alice" + authorUsername = "alice" + attachments = [ + UIImage(systemName: "photo")!, + UIImage(systemName: "photo")!, + UIImage(systemName: "photo")!, + UIImage(systemName: "photo")!, + ] + #endif + } + +} diff --git a/MastodonSDK/Sources/MastodonUI/Compose/StatusAuthorView.swift b/MastodonSDK/Sources/MastodonUI/Compose/StatusAuthorView.swift new file mode 100644 index 00000000..110dfb7f --- /dev/null +++ b/MastodonSDK/Sources/MastodonUI/Compose/StatusAuthorView.swift @@ -0,0 +1,44 @@ +// +// StatusAuthorView.swift +// +// +// Created by MainasuK Cirno on 2021-7-16. +// + +import SwiftUI +import Nuke +import NukeFLAnimatedImagePlugin +import FLAnimatedImage + +struct StatusAuthorView: View { + + let avatarImageURL: URL? + let name: String + let username: String + + var body: some View { + HStack(spacing: 5) { + AnimatedImage(imageURL: avatarImageURL) + .frame(width: 42, height: 42) + .cornerRadius(4) + VStack(alignment: .leading) { + Text(name) + .font(.headline) + Text("@" + username) + .font(.subheadline) + .foregroundColor(.secondary) + } + Spacer() + } + } +} + +struct StatusAuthorView_Previews: PreviewProvider { + static var previews: some View { + StatusAuthorView( + avatarImageURL: URL(string: "https://upload.wikimedia.org/wikipedia/commons/2/2c/Rotating_earth_%28large%29.gif"), + name: "Alice", + username: "alice" + ) + } +} diff --git a/MastodonSDK/Sources/MastodonUI/Compose/TextEditorView.swift b/MastodonSDK/Sources/MastodonUI/Compose/TextEditorView.swift new file mode 100644 index 00000000..4f087f6b --- /dev/null +++ b/MastodonSDK/Sources/MastodonUI/Compose/TextEditorView.swift @@ -0,0 +1,80 @@ +// +// TextEditorView.swift +// +// +// Created by MainasuK Cirno on 2021-7-16. +// + +import UIKit +import SwiftUI + +public struct TextEditorView: UIViewRepresentable { + + @Binding var string: String + + let width: CGFloat + let attributedString: NSAttributedString + + public init( + string: Binding, + width: CGFloat, + attributedString: NSAttributedString + ) { + self._string = string + self.width = width + self.attributedString = attributedString + } + + public func makeUIView(context: Context) -> UITextView { + let textView = UITextView(frame: .zero) + + textView.isScrollEnabled = false + textView.font = .preferredFont(forTextStyle: .body) + textView.textColor = .label + + textView.delegate = context.coordinator + + textView.translatesAutoresizingMaskIntoConstraints = false + let widthLayoutConstraint = textView.widthAnchor.constraint(equalToConstant: 100) + widthLayoutConstraint.priority = .required - 1 + context.coordinator.widthLayoutConstraint = widthLayoutConstraint + + + return textView + } + + public func updateUIView(_ textView: UITextView, context: Context) { + // update content + // textView.attributedText = attributedString + textView.text = string + + // update layout + context.coordinator.updateLayout(width: width) + } + + public func makeCoordinator() -> Coordinator { + Coordinator(self) + } + + public class Coordinator: NSObject, UITextViewDelegate { + var parent: TextEditorView + var widthLayoutConstraint: NSLayoutConstraint? + + init(_ parent: TextEditorView) { + self.parent = parent + } + + public func textViewDidChange(_ textView: UITextView) { + parent.string = textView.text + } + + func updateLayout(width: CGFloat) { + guard let widthLayoutConstraint = widthLayoutConstraint else { return } + widthLayoutConstraint.constant = width + widthLayoutConstraint.isActive = true + } + } + +} + + diff --git a/ShareActionExtension/Info.plist b/ShareActionExtension/Info.plist index ea226b26..52924bee 100644 --- a/ShareActionExtension/Info.plist +++ b/ShareActionExtension/Info.plist @@ -17,24 +17,24 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 1.0 + $(MARKETING_VERSION) CFBundleVersion - 1 + $(CURRENT_PROJECT_VERSION) NSExtension NSExtensionAttributes - NSExtensionActivationRule - - NSExtensionActivationSupportsImageWithMaxCount - 4 - NSExtensionActivationSupportsMovieWithMaxCount - 1 - NSExtensionActivationSupportsText - - NSExtensionActivationSupportsWebURLWithMaxCount - 1 - + NSExtensionActivationRule + + NSExtensionActivationSupportsImageWithMaxCount + 4 + NSExtensionActivationSupportsMovieWithMaxCount + 1 + NSExtensionActivationSupportsText + + NSExtensionActivationSupportsWebURLWithMaxCount + 1 + NSExtensionMainStoryboard MainInterface diff --git a/ShareActionExtension/ShareViewController.swift b/ShareActionExtension/ShareViewController.swift index c0479c96..e44e3f9f 100644 --- a/ShareActionExtension/ShareViewController.swift +++ b/ShareActionExtension/ShareViewController.swift @@ -9,6 +9,7 @@ import os.log import UIKit import Combine import MastodonUI +import SwiftUI class ShareViewController: UIViewController { @@ -45,19 +46,8 @@ class ShareViewController: UIViewController { return barButtonItem }() -// let tableView: ComposeTableView = { -// let tableView = ComposeTableView() -// tableView.register(ComposeStatusContentTableViewCell.self, forCellReuseIdentifier: String(describing: ComposeStatusContentTableViewCell.self)) -// tableView.register(ComposeStatusAttachmentTableViewCell.self, forCellReuseIdentifier: String(describing: ComposeStatusAttachmentTableViewCell.self)) -// tableView.alwaysBounceVertical = true -// tableView.separatorStyle = .none -// tableView.tableFooterView = UIView() -// return tableView -// }() - } - extension ShareViewController { override func viewDidLoad() { @@ -74,6 +64,21 @@ extension ShareViewController { } .store(in: &disposeBag) + let hostingViewController = UIHostingController( + rootView: ComposeView().environmentObject(viewModel.composeViewModel) + ) + addChild(hostingViewController) + view.addSubview(hostingViewController.view) + hostingViewController.view.translatesAutoresizingMaskIntoConstraints = false + view.addSubview(hostingViewController.view) + NSLayoutConstraint.activate([ + hostingViewController.view.topAnchor.constraint(equalTo: view.topAnchor), + hostingViewController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor), + hostingViewController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor), + hostingViewController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor), + ]) + hostingViewController.didMove(toParent: self) + // viewModel.authentication // .receive(on: DispatchQueue.main) // .sink { [weak self] result in diff --git a/ShareActionExtension/ShareViewModel.swift b/ShareActionExtension/ShareViewModel.swift index bea00c51..8384eb12 100644 --- a/ShareActionExtension/ShareViewModel.swift +++ b/ShareActionExtension/ShareViewModel.swift @@ -10,6 +10,7 @@ import Foundation import Combine import CoreData import CoreDataStack +import MastodonUI final class ShareViewModel { @@ -27,6 +28,7 @@ final class ShareViewModel { let isFetchAuthentication = CurrentValueSubject(true) let isBusy = CurrentValueSubject(true) let isValid = CurrentValueSubject(false) + let composeViewModel = ComposeViewModel() init() { viewDidAppear.receive(on: DispatchQueue.main) From 1cdbd7fa2a3dcd9049eb49b2011a48e83257a17d Mon Sep 17 00:00:00 2001 From: CMK Date: Mon, 19 Jul 2021 17:12:45 +0800 Subject: [PATCH 03/11] feat: make UI works --- Mastodon.xcodeproj/project.pbxproj | 391 +++++++++++++----- .../xcschemes/xcschememanagement.plist | 10 +- .../xcshareddata/swiftpm/Package.resolved | 9 + Mastodon/Extension/ActiveLabel.swift | 11 +- Mastodon/Helper/MastodonField.swift | 112 ++--- Mastodon/Helper/MastodonMetricFormatter.swift | 4 +- Mastodon/Helper/MastodonRegex.swift | 8 +- .../MastodonStatusContent+Appearance.swift | 2 +- .../MastodonStatusContent+ParseResult.swift | 18 +- Mastodon/Helper/MastodonStatusContent.swift | 10 +- .../Preference/AppearancePreference.swift | 3 +- .../Preference/NotificationPreference.swift | 1 + Mastodon/Preference/SplashPreference.swift | 12 - Mastodon/Preference/ThemePreference.swift | 1 + .../Protocol/AvatarConfigurableView.swift | 13 +- ...seStatusAttachmentCollectionViewCell.swift | 9 +- .../ComposeStatusContentTableViewCell.swift | 6 +- .../Scene/Compose/ComposeViewController.swift | 18 +- ...wift => ComposeViewModel+DataSource.swift} | 2 +- ...tachmentContainerView+EmptyStateView.swift | 1 + .../Compose/View/ComposeToolbarView.swift | 3 - .../Compose/View/ReplicaStatusView.swift | 23 +- .../View/StatusContentWarningEditorView.swift | 23 +- ...meTimelineViewController+DebugAction.swift | 12 - .../Header/ProfileHeaderViewController.swift | 2 +- .../MastodonAttachmentService.swift | 6 +- Mastodon/Service/ThemeService/Theme.swift | 6 +- .../ThemeService+Appearance.swift | 57 +++ .../Service/ThemeService/ThemeService.swift | 48 --- Mastodon/Supporting Files/AppDelegate.swift | 1 + MastodonSDK/Package.swift | 7 +- .../NSLayoutConstraint.swift | 4 +- .../MastodonExtension}/UserDefaults.swift | 5 +- .../MastodonUI/Compose/ComposeView.swift | 68 --- .../MastodonUI/Compose/ComposeViewModel.swift | 46 --- .../Service/KeyboardResponderService.swift | 10 +- .../{ => SwiftUI}/AnimatedImage.swift | 14 +- .../Vendor/ItemProviderLoader.swift | 34 +- .../Vendor}/TwitterTextEditor+String.swift | 10 +- .../MastodonUI/Vendor}/UIViewPreview.swift | 0 .../View/Button/HighlightDimmableButton.swift | 12 +- .../Button}/RoundedEdgesButton.swift | 0 Podfile | 5 + Podfile.lock | 2 +- .../Scene/ShareViewController.swift | 228 ++++++++++ .../{ => Scene}/ShareViewModel.swift | 87 +++- .../Scene/View/ComposeToolbarView.swift | 246 +++++++++++ .../Scene/View/ComposeView.swift | 117 ++++++ .../Scene/View/ComposeViewModel.swift | 80 ++++ .../Scene/View/ContentWarningEditorView.swift | 48 +++ .../Scene/View/StatusAttachmentView.swift | 65 +++ .../View/StatusAttachmentViewModel.swift | 104 +++++ .../Scene/View}/StatusAuthorView.swift | 1 + .../Scene/View/StatusEditorView.swift | 25 +- .../ShareActionExtension.entitlements | 10 + .../ShareViewController.swift | 107 ----- 56 files changed, 1548 insertions(+), 609 deletions(-) delete mode 100644 Mastodon/Preference/SplashPreference.swift rename Mastodon/Scene/Compose/{TableViewCell => CollectionViewCell}/ComposeStatusContentTableViewCell.swift (95%) rename Mastodon/Scene/Compose/{ComposeViewModel+Diffable.swift => ComposeViewModel+DataSource.swift} (99%) create mode 100644 Mastodon/Service/ThemeService/ThemeService+Appearance.swift rename {Mastodon/Extension => MastodonSDK/Sources/MastodonExtension}/NSLayoutConstraint.swift (67%) rename {Mastodon/Extension => MastodonSDK/Sources/MastodonExtension}/UserDefaults.swift (81%) delete mode 100644 MastodonSDK/Sources/MastodonUI/Compose/ComposeView.swift delete mode 100644 MastodonSDK/Sources/MastodonUI/Compose/ComposeViewModel.swift rename {Mastodon => MastodonSDK/Sources/MastodonUI}/Service/KeyboardResponderService.swift (90%) rename MastodonSDK/Sources/MastodonUI/{ => SwiftUI}/AnimatedImage.swift (78%) rename Mastodon/Vender/PHPickerResultLoader.swift => MastodonSDK/Sources/MastodonUI/Vendor/ItemProviderLoader.swift (74%) rename {Mastodon/Vender => MastodonSDK/Sources/MastodonUI/Vendor}/TwitterTextEditor+String.swift (84%) rename {Mastodon/Vender => MastodonSDK/Sources/MastodonUI/Vendor}/UIViewPreview.swift (100%) rename {Mastodon/Scene/Share => MastodonSDK/Sources/MastodonUI}/View/Button/HighlightDimmableButton.swift (59%) rename MastodonSDK/Sources/MastodonUI/{ => View/Button}/RoundedEdgesButton.swift (100%) create mode 100644 ShareActionExtension/Scene/ShareViewController.swift rename ShareActionExtension/{ => Scene}/ShareViewModel.swift (54%) create mode 100644 ShareActionExtension/Scene/View/ComposeToolbarView.swift create mode 100644 ShareActionExtension/Scene/View/ComposeView.swift create mode 100644 ShareActionExtension/Scene/View/ComposeViewModel.swift create mode 100644 ShareActionExtension/Scene/View/ContentWarningEditorView.swift create mode 100644 ShareActionExtension/Scene/View/StatusAttachmentView.swift create mode 100644 ShareActionExtension/Scene/View/StatusAttachmentViewModel.swift rename {MastodonSDK/Sources/MastodonUI/Compose => ShareActionExtension/Scene/View}/StatusAuthorView.swift (98%) rename MastodonSDK/Sources/MastodonUI/Compose/TextEditorView.swift => ShareActionExtension/Scene/View/StatusEditorView.swift (77%) create mode 100644 ShareActionExtension/ShareActionExtension.entitlements delete mode 100644 ShareActionExtension/ShareViewController.swift diff --git a/Mastodon.xcodeproj/project.pbxproj b/Mastodon.xcodeproj/project.pbxproj index ae5393fe..ea21a7df 100644 --- a/Mastodon.xcodeproj/project.pbxproj +++ b/Mastodon.xcodeproj/project.pbxproj @@ -67,12 +67,10 @@ 2D38F20825CD491300561493 /* DisposeBagCollectable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D38F20725CD491300561493 /* DisposeBagCollectable.swift */; }; 2D3F9E0425DFA133004262D9 /* UITapGestureRecognizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D3F9E0325DFA133004262D9 /* UITapGestureRecognizer.swift */; }; 2D42FF6125C8177C004A627A /* ActiveLabel in Frameworks */ = {isa = PBXBuildFile; productRef = 2D42FF6025C8177C004A627A /* ActiveLabel */; }; - 2D42FF6B25C817D2004A627A /* MastodonStatusContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D42FF6A25C817D2004A627A /* MastodonStatusContent.swift */; }; 2D42FF7E25C82218004A627A /* ActionToolBarContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D42FF7D25C82218004A627A /* ActionToolBarContainer.swift */; }; 2D42FF8525C8224F004A627A /* HitTestExpandedButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D42FF8425C8224F004A627A /* HitTestExpandedButton.swift */; }; 2D42FF8F25C8228A004A627A /* UIButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D42FF8E25C8228A004A627A /* UIButton.swift */; }; 2D45E5BF25C9549700A6D639 /* PublicTimelineViewModel+State.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D45E5BE25C9549700A6D639 /* PublicTimelineViewModel+State.swift */; }; - 2D46975E25C2A54100CF4AA9 /* NSLayoutConstraint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D46975D25C2A54100CF4AA9 /* NSLayoutConstraint.swift */; }; 2D4AD89C263165B500613EFC /* SuggestionAccountCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D4AD89B263165B500613EFC /* SuggestionAccountCollectionViewCell.swift */; }; 2D4AD8A226316CD200613EFC /* SelectedAccountSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D4AD8A126316CD200613EFC /* SelectedAccountSection.swift */; }; 2D4AD8A826316D3500613EFC /* SelectedAccountItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D4AD8A726316D3500613EFC /* SelectedAccountItem.swift */; }; @@ -141,6 +139,7 @@ 2DF75BB925D1474100694EC8 /* ManagedObjectObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DF75BB825D1474100694EC8 /* ManagedObjectObserver.swift */; }; 2DF75BC725D1475D00694EC8 /* ManagedObjectContextObjectsDidChange.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DF75BC625D1475D00694EC8 /* ManagedObjectContextObjectsDidChange.swift */; }; 2DFAD5372617010500F9EE7C /* SearchResultTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DFAD5362617010500F9EE7C /* SearchResultTableViewCell.swift */; }; + 4278334D6033AEEE0A1C5155 /* Pods_ShareActionExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A32B0CACBF35F4CC3CFAA043 /* Pods_ShareActionExtension.framework */; }; 5B24BBDA262DB14800A9381B /* ReportViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B24BBD7262DB14800A9381B /* ReportViewModel.swift */; }; 5B24BBDB262DB14800A9381B /* ReportViewModel+Diffable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B24BBD8262DB14800A9381B /* ReportViewModel+Diffable.swift */; }; 5B24BBE2262DB19100A9381B /* APIService+Report.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B24BBE1262DB19100A9381B /* APIService+Report.swift */; }; @@ -187,7 +186,6 @@ DB02CDBF2625AE5000D0A2AF /* AdaptiveUserInterfaceStyleBarButtonItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB02CDBE2625AE5000D0A2AF /* AdaptiveUserInterfaceStyleBarButtonItem.swift */; }; DB03F7EB268976B5007B274C /* MastodonMeta in Frameworks */ = {isa = PBXBuildFile; productRef = DB03F7EA268976B5007B274C /* MastodonMeta */; }; DB03F7ED268976B5007B274C /* MetaTextView in Frameworks */ = {isa = PBXBuildFile; productRef = DB03F7EC268976B5007B274C /* MetaTextView */; }; - DB03F7F026899097007B274C /* ComposeStatusContentTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB03F7EF26899097007B274C /* ComposeStatusContentTableViewCell.swift */; }; DB03F7F32689AEA3007B274C /* ComposeRepliedToStatusContentTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB03F7F22689AEA3007B274C /* ComposeRepliedToStatusContentTableViewCell.swift */; }; DB03F7F52689B782007B274C /* ComposeTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB03F7F42689B782007B274C /* ComposeTableView.swift */; }; DB040ED126538E3D00BEE9D8 /* Trie.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB040ED026538E3C00BEE9D8 /* Trie.swift */; }; @@ -196,7 +194,6 @@ DB0E2D2E26833FF700865C3C /* NukeFLAnimatedImagePlugin in Frameworks */ = {isa = PBXBuildFile; productRef = DB0E2D2D26833FF700865C3C /* NukeFLAnimatedImagePlugin */; }; DB0F8150264D1E2500F2A12B /* PickServerLoaderTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0F814F264D1E2500F2A12B /* PickServerLoaderTableViewCell.swift */; }; DB118A8225E4B6E600FAB162 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DB118A8125E4B6E600FAB162 /* Preview Assets.xcassets */; }; - DB118A8C25E4BFB500FAB162 /* HighlightDimmableButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB118A8B25E4BFB500FAB162 /* HighlightDimmableButton.swift */; }; DB1D186C25EF5BA7003F1F23 /* PollTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB1D186B25EF5BA7003F1F23 /* PollTableView.swift */; }; DB1D842C26551A1C000346B3 /* StatusProvider+StatusTableViewKeyCommandNavigateable.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB1D842B26551A1C000346B3 /* StatusProvider+StatusTableViewKeyCommandNavigateable.swift */; }; DB1D842E26552C4D000346B3 /* StatusTableViewControllerNavigateable.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB1D842D26552C4D000346B3 /* StatusTableViewControllerNavigateable.swift */; }; @@ -206,8 +203,6 @@ DB1D84382657B275000346B3 /* SegmentedControlNavigateable.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB1D84372657B275000346B3 /* SegmentedControlNavigateable.swift */; }; DB1E346825F518E20079D7DF /* CategoryPickerSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB1E346725F518E20079D7DF /* CategoryPickerSection.swift */; }; DB1E347825F519300079D7DF /* PickServerItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB1E347725F519300079D7DF /* PickServerItem.swift */; }; - DB1EE7AE267F3071000CC337 /* MastodonStatusContent+ParseResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB1EE7AD267F3071000CC337 /* MastodonStatusContent+ParseResult.swift */; }; - DB1EE7B0267F3088000CC337 /* MastodonStatusContent+Appearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB1EE7AF267F3088000CC337 /* MastodonStatusContent+Appearance.swift */; }; DB1EE7B2267F9525000CC337 /* StatusProvider+StatusNodeDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB1EE7B1267F9525000CC337 /* StatusProvider+StatusNodeDelegate.swift */; }; DB1FD43625F26899004CFCFC /* MastodonPickServerViewModel+LoadIndexedServerState.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB1FD43525F26899004CFCFC /* MastodonPickServerViewModel+LoadIndexedServerState.swift */; }; DB1FD44425F26CCC004CFCFC /* PickServerSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB1FD44325F26CCC004CFCFC /* PickServerSection.swift */; }; @@ -216,21 +211,28 @@ DB221B16260C395900AEFE46 /* CustomEmojiPickerInputViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB221B15260C395900AEFE46 /* CustomEmojiPickerInputViewModel.swift */; }; DB297B1B2679FAE200704C90 /* PlaceholderImageCacheService.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB297B1A2679FAE200704C90 /* PlaceholderImageCacheService.swift */; }; DB2B3ABC25E37E15007045F9 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = DB2B3ABE25E37E15007045F9 /* InfoPlist.strings */; }; - DB2B3AE925E38850007045F9 /* UIViewPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB2B3AE825E38850007045F9 /* UIViewPreview.swift */; }; DB2F073525E8ECF000957B2D /* AuthenticationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB2F073325E8ECF000957B2D /* AuthenticationViewModel.swift */; }; DB2FF510260B113300ADA9FE /* ComposeStatusPollExpiresOptionCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB2FF50F260B113300ADA9FE /* ComposeStatusPollExpiresOptionCollectionViewCell.swift */; }; DB35FC1F2612F1D9006193C9 /* ProfileRelationshipActionButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB35FC1E2612F1D9006193C9 /* ProfileRelationshipActionButton.swift */; }; DB35FC252612FD7A006193C9 /* ProfileFieldView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB35FC242612FD7A006193C9 /* ProfileFieldView.swift */; }; - DB35FC2F26130172006193C9 /* MastodonField.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB35FC2E26130172006193C9 /* MastodonField.swift */; }; DB36679D268AB91B0027D07F /* ComposeStatusAttachmentTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB36679C268AB91B0027D07F /* ComposeStatusAttachmentTableViewCell.swift */; }; DB36679F268ABAF20027D07F /* ComposeStatusAttachmentSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB36679E268ABAF20027D07F /* ComposeStatusAttachmentSection.swift */; }; DB3667A1268ABB2E0027D07F /* ComposeStatusAttachmentItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB3667A0268ABB2E0027D07F /* ComposeStatusAttachmentItem.swift */; }; DB3667A4268AE2370027D07F /* ComposeStatusPollTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB3667A3268AE2370027D07F /* ComposeStatusPollTableViewCell.swift */; }; DB3667A6268AE2620027D07F /* ComposeStatusPollSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB3667A5268AE2620027D07F /* ComposeStatusPollSection.swift */; }; DB3667A8268AE2900027D07F /* ComposeStatusPollItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB3667A7268AE2900027D07F /* ComposeStatusPollItem.swift */; }; - DB3667CA268B14A80027D07F /* ReplicaStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB3667C9268B14A80027D07F /* ReplicaStatusView.swift */; }; DB3D0FF325BAA61700EAA174 /* AlamofireImage in Frameworks */ = {isa = PBXBuildFile; productRef = DB3D0FF225BAA61700EAA174 /* AlamofireImage */; }; DB3D100D25BAA75E00EAA174 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = DB3D100F25BAA75E00EAA174 /* Localizable.strings */; }; + DB41ED7A26A54D4400F58330 /* MastodonStatusContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBC24D726A54BCB00398BB9 /* MastodonStatusContent.swift */; }; + DB41ED7B26A54D4D00F58330 /* MastodonStatusContent+ParseResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBC24D926A54BCB00398BB9 /* MastodonStatusContent+ParseResult.swift */; }; + DB41ED7C26A54D5500F58330 /* MastodonStatusContent+Appearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBC24DB26A54BCB00398BB9 /* MastodonStatusContent+Appearance.swift */; }; + DB41ED7E26A54D6D00F58330 /* Fuzi in Frameworks */ = {isa = PBXBuildFile; productRef = DB41ED7D26A54D6D00F58330 /* Fuzi */; }; + DB41ED8026A54D7C00F58330 /* AlamofireImage in Frameworks */ = {isa = PBXBuildFile; productRef = DB41ED7F26A54D7C00F58330 /* AlamofireImage */; }; + DB41ED8226A54D8A00F58330 /* MastodonMeta in Frameworks */ = {isa = PBXBuildFile; productRef = DB41ED8126A54D8A00F58330 /* MastodonMeta */; }; + DB41ED8426A54D8A00F58330 /* MetaTextView in Frameworks */ = {isa = PBXBuildFile; productRef = DB41ED8326A54D8A00F58330 /* MetaTextView */; }; + DB41ED8926A54F4000F58330 /* ComposeStatusAttachmentCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6B351D2601FAEE00DC1E11 /* ComposeStatusAttachmentCollectionViewCell.swift */; }; + DB41ED8A26A54F4C00F58330 /* AttachmentContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB8190C52601FF0400020C08 /* AttachmentContainerView.swift */; }; + DB41ED8B26A54F5800F58330 /* AttachmentContainerView+EmptyStateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB9A486B26032AC1008B817C /* AttachmentContainerView+EmptyStateView.swift */; }; DB427DD625BAA00100D1B89D /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB427DD525BAA00100D1B89D /* AppDelegate.swift */; }; DB427DD825BAA00100D1B89D /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB427DD725BAA00100D1B89D /* SceneDelegate.swift */; }; DB427DDD25BAA00100D1B89D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = DB427DDB25BAA00100D1B89D /* Main.storyboard */; }; @@ -250,7 +252,6 @@ DB4481B925EE289600BEFB67 /* UITableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB4481B825EE289600BEFB67 /* UITableView.swift */; }; DB4481C625EE2ADA00BEFB67 /* PollSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB4481C525EE2ADA00BEFB67 /* PollSection.swift */; }; DB4481CC25EE2AFE00BEFB67 /* PollItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB4481CB25EE2AFE00BEFB67 /* PollItem.swift */; }; - DB4563BD25E11A24004DA0B9 /* KeyboardResponderService.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB4563BC25E11A24004DA0B9 /* KeyboardResponderService.swift */; }; DB45FAB625CA5485005A8AC7 /* UIAlertController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB45FAB525CA5485005A8AC7 /* UIAlertController.swift */; }; DB45FAD725CA6C76005A8AC7 /* UIBarButtonItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB45FAD625CA6C76005A8AC7 /* UIBarButtonItem.swift */; }; DB45FADD25CA6F6B005A8AC7 /* APIService+CoreData+MastodonUser.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB45FADC25CA6F6B005A8AC7 /* APIService+CoreData+MastodonUser.swift */; }; @@ -281,12 +282,9 @@ DB4FFC2B269EC39600D62E92 /* SearchToSearchDetailViewControllerAnimatedTransitioning.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB4FFC29269EC39600D62E92 /* SearchToSearchDetailViewControllerAnimatedTransitioning.swift */; }; DB4FFC2C269EC39600D62E92 /* SearchTransitionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB4FFC2A269EC39600D62E92 /* SearchTransitionController.swift */; }; DB5086A525CC0B7000C2C187 /* AvatarBarButtonItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB5086A425CC0B7000C2C187 /* AvatarBarButtonItem.swift */; }; - DB5086AB25CC0BBB00C2C187 /* AvatarConfigurableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB5086AA25CC0BBB00C2C187 /* AvatarConfigurableView.swift */; }; - DB5086BE25CC0D9900C2C187 /* SplashPreference.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB5086BD25CC0D9900C2C187 /* SplashPreference.swift */; }; DB51D172262832380062B7A1 /* BlurHashDecode.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB51D170262832380062B7A1 /* BlurHashDecode.swift */; }; DB51D173262832380062B7A1 /* BlurHashEncode.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB51D171262832380062B7A1 /* BlurHashEncode.swift */; }; DB52D33A26839DD800D43133 /* ImageTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB52D33926839DD800D43133 /* ImageTask.swift */; }; - DB55D33025FB630A0002F825 /* TwitterTextEditor+String.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB55D32F25FB630A0002F825 /* TwitterTextEditor+String.swift */; }; DB564BD0269F2F83001E39A7 /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = DB564BCE269F2F83001E39A7 /* Localizable.stringsdict */; }; DB564BD3269F3B35001E39A7 /* StatusFilterService.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB564BD2269F3B35001E39A7 /* StatusFilterService.swift */; }; DB59F0FE25EF5D96001F1DAB /* StatusProvider+UITableViewDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB59F0FD25EF5D96001F1DAB /* StatusProvider+UITableViewDelegate.swift */; }; @@ -307,7 +305,7 @@ DB6180F826391D660018D199 /* MediaPreviewingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6180F726391D660018D199 /* MediaPreviewingViewController.swift */; }; DB6180FA26391F2E0018D199 /* MediaPreviewViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6180F926391F2E0018D199 /* MediaPreviewViewModel.swift */; }; DB63BE7F268DD1070011D3F9 /* NotificationViewController+StatusProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB63BE7E268DD1070011D3F9 /* NotificationViewController+StatusProvider.swift */; }; - DB66728C25F9F8DC00D60309 /* ComposeViewModel+Diffable.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB66728B25F9F8DC00D60309 /* ComposeViewModel+Diffable.swift */; }; + DB66728C25F9F8DC00D60309 /* ComposeViewModel+DataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB66728B25F9F8DC00D60309 /* ComposeViewModel+DataSource.swift */; }; DB66729625F9F91600D60309 /* ComposeStatusSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB66729525F9F91600D60309 /* ComposeStatusSection.swift */; }; DB66729C25F9F91F00D60309 /* ComposeStatusItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB66729B25F9F91F00D60309 /* ComposeStatusItem.swift */; }; DB68045B2636DC6A00430867 /* MastodonNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB68045A2636DC6A00430867 /* MastodonNotification.swift */; }; @@ -330,7 +328,6 @@ DB6B35182601FA3400DC1E11 /* MastodonAttachmentService.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6B35172601FA3400DC1E11 /* MastodonAttachmentService.swift */; }; DB6B351E2601FAEE00DC1E11 /* ComposeStatusAttachmentCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6B351D2601FAEE00DC1E11 /* ComposeStatusAttachmentCollectionViewCell.swift */; }; DB6C8C0F25F0A6AE00AAA452 /* Mastodon+Entity+Error.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6C8C0E25F0A6AE00AAA452 /* Mastodon+Entity+Error.swift */; }; - DB6D1B24263684C600ACB481 /* UserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6D1B23263684C600ACB481 /* UserDefaults.swift */; }; DB6D1B3D2636857500ACB481 /* AppearancePreference.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6D1B3C2636857500ACB481 /* AppearancePreference.swift */; }; DB6D1B44263691CF00ACB481 /* Mastodon+API+Subscriptions+Policy.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6D1B43263691CF00ACB481 /* Mastodon+API+Subscriptions+Policy.swift */; }; DB6D9F3526351B7A008423CD /* NotificationService+Decrypt.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6D9F3426351B7A008423CD /* NotificationService+Decrypt.swift */; }; @@ -344,7 +341,6 @@ DB6D9F7D26358ED4008423CD /* SettingsSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6D9F7C26358ED4008423CD /* SettingsSection.swift */; }; DB6D9F8426358EEC008423CD /* SettingsItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6D9F8326358EEC008423CD /* SettingsItem.swift */; }; DB6D9F9726367249008423CD /* SettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6D9F9626367249008423CD /* SettingsViewController.swift */; }; - DB6F5E2F264E5518009108F4 /* MastodonRegex.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6F5E2E264E5518009108F4 /* MastodonRegex.swift */; }; DB6F5E32264E7410009108F4 /* TwitterTextEditor in Frameworks */ = {isa = PBXBuildFile; productRef = DB6F5E31264E7410009108F4 /* TwitterTextEditor */; }; DB6F5E33264E7410009108F4 /* TwitterTextEditor in Embed Frameworks */ = {isa = PBXBuildFile; productRef = DB6F5E31264E7410009108F4 /* TwitterTextEditor */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; DB6F5E35264E78E7009108F4 /* AutoCompleteViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6F5E34264E78E7009108F4 /* AutoCompleteViewController.swift */; }; @@ -407,7 +403,6 @@ DB98338725C945ED00AD9700 /* Strings.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB98338525C945ED00AD9700 /* Strings.swift */; }; DB98338825C945ED00AD9700 /* Assets.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB98338625C945ED00AD9700 /* Assets.swift */; }; DB98339C25C96DE600AD9700 /* APIService+Account.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB98339B25C96DE600AD9700 /* APIService+Account.swift */; }; - DB9A485C2603010E008B817C /* PHPickerResultLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB9A485B2603010E008B817C /* PHPickerResultLoader.swift */; }; DB9A486C26032AC1008B817C /* AttachmentContainerView+EmptyStateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB9A486B26032AC1008B817C /* AttachmentContainerView+EmptyStateView.swift */; }; DB9A487E2603456B008B817C /* UITextView+Placeholder in Frameworks */ = {isa = PBXBuildFile; productRef = DB9A487D2603456B008B817C /* UITextView+Placeholder */; }; DB9A488A26034D40008B817C /* ComposeViewModel+PublishState.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB9A488926034D40008B817C /* ComposeViewModel+PublishState.swift */; }; @@ -454,7 +449,6 @@ DBAE3F8E2616E0B1004B8251 /* APIService+Block.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBAE3F8D2616E0B1004B8251 /* APIService+Block.swift */; }; DBAE3F942616E28B004B8251 /* APIService+Follow.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBAE3F932616E28B004B8251 /* APIService+Follow.swift */; }; DBAE3F9E2616E308004B8251 /* APIService+Mute.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBAE3F9D2616E308004B8251 /* APIService+Mute.swift */; }; - DBAE3FA92617106E004B8251 /* MastodonMetricFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBAE3FA82617106E004B8251 /* MastodonMetricFormatter.swift */; }; DBAE3FAF26172FC0004B8251 /* RemoteProfileViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBAE3FAE26172FC0004B8251 /* RemoteProfileViewModel.swift */; }; DBAEDE5C267A058D00D25FF5 /* BlurhashImageCacheService.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBAEDE5B267A058D00D25FF5 /* BlurhashImageCacheService.swift */; }; DBAEDE5F267A0B1500D25FF5 /* Nuke in Frameworks */ = {isa = PBXBuildFile; productRef = DBAEDE5E267A0B1500D25FF5 /* Nuke */; }; @@ -473,6 +467,36 @@ DBB5256E2612D5A1002F1F29 /* ProfileStatusDashboardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB5256D2612D5A1002F1F29 /* ProfileStatusDashboardView.swift */; }; DBB525852612D6DD002F1F29 /* ProfileStatusDashboardMeterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB525842612D6DD002F1F29 /* ProfileStatusDashboardMeterView.swift */; }; DBB9759C262462E1004620BD /* ThreadMetaView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB9759B262462E1004620BD /* ThreadMetaView.swift */; }; + DBBC24A826A52F9000398BB9 /* ComposeToolbarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBC24A726A52F9000398BB9 /* ComposeToolbarView.swift */; }; + DBBC24AA26A5301B00398BB9 /* MastodonSDK in Frameworks */ = {isa = PBXBuildFile; productRef = DBBC24A926A5301B00398BB9 /* MastodonSDK */; }; + DBBC24AC26A53D9300398BB9 /* ComposeStatusContentTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBC24AB26A53D9300398BB9 /* ComposeStatusContentTableViewCell.swift */; }; + DBBC24AE26A53DC100398BB9 /* ReplicaStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBC24AD26A53DC100398BB9 /* ReplicaStatusView.swift */; }; + DBBC24B026A53DF900398BB9 /* ReplicaStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBC24AD26A53DC100398BB9 /* ReplicaStatusView.swift */; }; + DBBC24B226A53ED200398BB9 /* ActiveLabel in Frameworks */ = {isa = PBXBuildFile; productRef = DBBC24B126A53ED200398BB9 /* ActiveLabel */; }; + DBBC24B326A53EE700398BB9 /* ActiveLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DF123A625C3B0210020F248 /* ActiveLabel.swift */; }; + DBBC24B526A540AE00398BB9 /* AvatarConfigurableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBC24B426A540AE00398BB9 /* AvatarConfigurableView.swift */; }; + DBBC24B626A5419700398BB9 /* ComposeStatusContentTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBC24AB26A53D9300398BB9 /* ComposeStatusContentTableViewCell.swift */; }; + DBBC24B826A5421800398BB9 /* CommonOSLog in Frameworks */ = {isa = PBXBuildFile; productRef = DBBC24B726A5421800398BB9 /* CommonOSLog */; }; + DBBC24B926A5426000398BB9 /* StatusContentWarningEditorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBC7A671260C897100E57475 /* StatusContentWarningEditorView.swift */; }; + DBBC24BC26A542F500398BB9 /* ThemeService.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBC24BB26A542F500398BB9 /* ThemeService.swift */; }; + DBBC24C026A5443100398BB9 /* MastodonTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBC24BE26A5443100398BB9 /* MastodonTheme.swift */; }; + DBBC24C126A5443100398BB9 /* SystemTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBC24BF26A5443100398BB9 /* SystemTheme.swift */; }; + DBBC24C426A544B900398BB9 /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBC24C326A544B900398BB9 /* Theme.swift */; }; + DBBC24C626A5456000398BB9 /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBC24C326A544B900398BB9 /* Theme.swift */; }; + DBBC24C726A5456400398BB9 /* SystemTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBC24BF26A5443100398BB9 /* SystemTheme.swift */; }; + DBBC24C826A5456400398BB9 /* ThemeService.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBC24BB26A542F500398BB9 /* ThemeService.swift */; }; + DBBC24C926A5456400398BB9 /* MastodonTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBC24BE26A5443100398BB9 /* MastodonTheme.swift */; }; + DBBC24CB26A546C000398BB9 /* ThemePreference.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBD376AB2692ECDB007FEC24 /* ThemePreference.swift */; }; + DBBC24CD26A5471E00398BB9 /* MastodonExtension in Frameworks */ = {isa = PBXBuildFile; productRef = DBBC24CC26A5471E00398BB9 /* MastodonExtension */; }; + DBBC24CF26A547AE00398BB9 /* ThemeService+Appearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBC24CE26A547AE00398BB9 /* ThemeService+Appearance.swift */; }; + DBBC24D126A5484F00398BB9 /* UITextView+Placeholder in Frameworks */ = {isa = PBXBuildFile; productRef = DBBC24D026A5484F00398BB9 /* UITextView+Placeholder */; }; + DBBC24D226A5488600398BB9 /* AvatarConfigurableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBC24B426A540AE00398BB9 /* AvatarConfigurableView.swift */; }; + DBBC24DC26A54BCB00398BB9 /* MastodonRegex.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBC24D626A54BCB00398BB9 /* MastodonRegex.swift */; }; + DBBC24DD26A54BCB00398BB9 /* MastodonStatusContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBC24D726A54BCB00398BB9 /* MastodonStatusContent.swift */; }; + DBBC24DE26A54BCB00398BB9 /* MastodonMetricFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBC24D826A54BCB00398BB9 /* MastodonMetricFormatter.swift */; }; + DBBC24DF26A54BCB00398BB9 /* MastodonStatusContent+ParseResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBC24D926A54BCB00398BB9 /* MastodonStatusContent+ParseResult.swift */; }; + DBBC24E026A54BCB00398BB9 /* MastodonField.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBC24DA26A54BCB00398BB9 /* MastodonField.swift */; }; + DBBC24E126A54BCB00398BB9 /* MastodonStatusContent+Appearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBC24DB26A54BCB00398BB9 /* MastodonStatusContent+Appearance.swift */; }; DBBE1B4525F3474B0081417A /* MastodonPickServerAppearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBE1B4425F3474B0081417A /* MastodonPickServerAppearance.swift */; }; DBBF1DBF2652401B00E5B703 /* AutoCompleteViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBF1DBE2652401B00E5B703 /* AutoCompleteViewModel.swift */; }; DBBF1DC226524D2900E5B703 /* AutoCompleteTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBF1DC126524D2900E5B703 /* AutoCompleteTableViewCell.swift */; }; @@ -514,11 +538,7 @@ DBCC3B9526157E6E0045B23D /* APIService+Relationship.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBCC3B9426157E6E0045B23D /* APIService+Relationship.swift */; }; DBCC3B9B261584A00045B23D /* PrivateNote.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBCC3B9A2615849F0045B23D /* PrivateNote.swift */; }; DBCCC71E25F73297007E1AB6 /* APIService+Reblog.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBCCC71D25F73297007E1AB6 /* APIService+Reblog.swift */; }; - DBD376A72692EA00007FEC24 /* ThemeService.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBD376A62692EA00007FEC24 /* ThemeService.swift */; }; - DBD376AA2692EA4F007FEC24 /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBD376A92692EA4F007FEC24 /* Theme.swift */; }; DBD376AC2692ECDB007FEC24 /* ThemePreference.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBD376AB2692ECDB007FEC24 /* ThemePreference.swift */; }; - DBD376AE2692EE0A007FEC24 /* MastodonTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBD376AD2692EE0A007FEC24 /* MastodonTheme.swift */; }; - DBD376B02692F20F007FEC24 /* SystemTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBD376AF2692F20F007FEC24 /* SystemTheme.swift */; }; DBD376B2269302A4007FEC24 /* UITableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBD376B1269302A4007FEC24 /* UITableViewCell.swift */; }; DBD9149025DF6D8D00903DFD /* APIService+Onboarding.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBD9148F25DF6D8D00903DFD /* APIService+Onboarding.swift */; }; DBE0821525CD382600FD6BBD /* MastodonRegisterViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBE0821425CD382600FD6BBD /* MastodonRegisterViewController.swift */; }; @@ -531,7 +551,6 @@ DBE3CE07261D6A0E00430CC6 /* FavoriteViewModel+Diffable.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBE3CE06261D6A0E00430CC6 /* FavoriteViewModel+Diffable.swift */; }; DBE3CE0D261D767100430CC6 /* FavoriteViewController+Provider.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBE3CE0C261D767100430CC6 /* FavoriteViewController+Provider.swift */; }; DBE3CE13261D7D4200430CC6 /* StatusTableViewControllerAspect.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBE3CE12261D7D4200430CC6 /* StatusTableViewControllerAspect.swift */; }; - DBE54ABF2636C889004E7C0B /* UserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6D1B23263684C600ACB481 /* UserDefaults.swift */; }; DBE54AC62636C89F004E7C0B /* NotificationPreference.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBE54AC52636C89F004E7C0B /* NotificationPreference.swift */; }; DBE54ACC2636C8FD004E7C0B /* NotificationPreference.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBE54AC52636C89F004E7C0B /* NotificationPreference.swift */; }; DBF1D24E269DAF5D00C1C08A /* SearchDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBF1D24D269DAF5D00C1C08A /* SearchDetailViewController.swift */; }; @@ -543,6 +562,13 @@ DBF96326262EC0A6001D8D25 /* AuthenticationServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DBF96325262EC0A6001D8D25 /* AuthenticationServices.framework */; }; DBF9814A265E24F500E4BA07 /* ProfileFieldCollectionViewHeaderFooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBF98149265E24F500E4BA07 /* ProfileFieldCollectionViewHeaderFooterView.swift */; }; DBF9814C265E339500E4BA07 /* ProfileFieldAddEntryCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBF9814B265E339500E4BA07 /* ProfileFieldAddEntryCollectionViewCell.swift */; }; + DBFEF05B26A57715006D7ED1 /* ComposeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBFEF05726A576EE006D7ED1 /* ComposeViewModel.swift */; }; + DBFEF05C26A57715006D7ED1 /* StatusEditorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBFEF05526A576EE006D7ED1 /* StatusEditorView.swift */; }; + DBFEF05D26A57715006D7ED1 /* ContentWarningEditorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBFEF05826A576EE006D7ED1 /* ContentWarningEditorView.swift */; }; + DBFEF05E26A57715006D7ED1 /* ComposeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBFEF05626A576EE006D7ED1 /* ComposeView.swift */; }; + DBFEF05F26A57715006D7ED1 /* StatusAuthorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBFEF05926A576EE006D7ED1 /* StatusAuthorView.swift */; }; + DBFEF06026A57715006D7ED1 /* StatusAttachmentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBFEF05A26A576EE006D7ED1 /* StatusAttachmentView.swift */; }; + DBFEF06326A577F2006D7ED1 /* StatusAttachmentViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBFEF06226A577F2006D7ED1 /* StatusAttachmentViewModel.swift */; }; EE93E8E8F9E0C39EAAEBD92F /* Pods_AppShared.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F4A2A2D7000E477CA459ADA9 /* Pods_AppShared.framework */; }; /* End PBXBuildFile section */ @@ -728,12 +754,10 @@ 2D38F1FD25CD481700561493 /* StatusProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusProvider.swift; sourceTree = ""; }; 2D38F20725CD491300561493 /* DisposeBagCollectable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisposeBagCollectable.swift; sourceTree = ""; }; 2D3F9E0325DFA133004262D9 /* UITapGestureRecognizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITapGestureRecognizer.swift; sourceTree = ""; }; - 2D42FF6A25C817D2004A627A /* MastodonStatusContent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MastodonStatusContent.swift; sourceTree = ""; }; 2D42FF7D25C82218004A627A /* ActionToolBarContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionToolBarContainer.swift; sourceTree = ""; }; 2D42FF8425C8224F004A627A /* HitTestExpandedButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HitTestExpandedButton.swift; sourceTree = ""; }; 2D42FF8E25C8228A004A627A /* UIButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIButton.swift; sourceTree = ""; }; 2D45E5BE25C9549700A6D639 /* PublicTimelineViewModel+State.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "PublicTimelineViewModel+State.swift"; sourceTree = ""; }; - 2D46975D25C2A54100CF4AA9 /* NSLayoutConstraint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSLayoutConstraint.swift; sourceTree = ""; }; 2D4AD89B263165B500613EFC /* SuggestionAccountCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuggestionAccountCollectionViewCell.swift; sourceTree = ""; }; 2D4AD8A126316CD200613EFC /* SelectedAccountSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectedAccountSection.swift; sourceTree = ""; }; 2D4AD8A726316D3500613EFC /* SelectedAccountItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectedAccountItem.swift; sourceTree = ""; }; @@ -826,6 +850,7 @@ 5BB04FE8262EFC300043BFF6 /* ReportedStatusTableviewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportedStatusTableviewCell.swift; sourceTree = ""; }; 5BB04FEE262F0DCB0043BFF6 /* ReportViewModel+Data.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ReportViewModel+Data.swift"; sourceTree = ""; }; 5BB04FF4262F0E6D0043BFF6 /* ReportSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportSection.swift; sourceTree = ""; }; + 5CE45680252519F42FEA2D13 /* Pods-ShareActionExtension.asdk - release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ShareActionExtension.asdk - release.xcconfig"; path = "Target Support Files/Pods-ShareActionExtension/Pods-ShareActionExtension.asdk - release.xcconfig"; sourceTree = ""; }; 5D03938F2612D259007FE196 /* WebViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebViewController.swift; sourceTree = ""; }; 5D0393952612D266007FE196 /* WebViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebViewModel.swift; sourceTree = ""; }; 5DA732CB2629CEF500A92342 /* UIView+Remove.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+Remove.swift"; sourceTree = ""; }; @@ -840,16 +865,20 @@ 5DF1057E25F88A4100D6C0D4 /* TouchBlockingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TouchBlockingView.swift; sourceTree = ""; }; 5DF1058425F88AE500D6C0D4 /* NeedsDependency+AVPlayerViewControllerDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NeedsDependency+AVPlayerViewControllerDelegate.swift"; sourceTree = ""; }; 5DFC35DE262068D20045711D /* SearchViewController+Follow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SearchViewController+Follow.swift"; sourceTree = ""; }; + 6130CBE4B26E3C976ACC1688 /* Pods-ShareActionExtension.asdk - debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ShareActionExtension.asdk - debug.xcconfig"; path = "Target Support Files/Pods-ShareActionExtension/Pods-ShareActionExtension.asdk - debug.xcconfig"; sourceTree = ""; }; 75E3471C898DDD9631729B6E /* Pods-Mastodon.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Mastodon.release.xcconfig"; path = "Target Support Files/Pods-Mastodon/Pods-Mastodon.release.xcconfig"; sourceTree = ""; }; + 77EE917BC055E6621C0452B6 /* Pods-ShareActionExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ShareActionExtension.debug.xcconfig"; path = "Target Support Files/Pods-ShareActionExtension/Pods-ShareActionExtension.debug.xcconfig"; sourceTree = ""; }; 7CEFFAE9AF9284B13C0A758D /* Pods-MastodonTests.asdk - debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MastodonTests.asdk - debug.xcconfig"; path = "Target Support Files/Pods-MastodonTests/Pods-MastodonTests.asdk - debug.xcconfig"; sourceTree = ""; }; 819CEC9DCAD8E8E7BD85A7BB /* Pods-Mastodon.asdk.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Mastodon.asdk.xcconfig"; path = "Target Support Files/Pods-Mastodon/Pods-Mastodon.asdk.xcconfig"; sourceTree = ""; }; 8850E70A1D5FF51432E43653 /* Pods-Mastodon-MastodonUITests.asdk - release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Mastodon-MastodonUITests.asdk - release.xcconfig"; path = "Target Support Files/Pods-Mastodon-MastodonUITests/Pods-Mastodon-MastodonUITests.asdk - release.xcconfig"; sourceTree = ""; }; 8ED8C4B1F1BA2DCFF2926BB1 /* Pods-Mastodon-NotificationService.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Mastodon-NotificationService.debug.xcconfig"; path = "Target Support Files/Pods-Mastodon-NotificationService/Pods-Mastodon-NotificationService.debug.xcconfig"; sourceTree = ""; }; 9553C689FFA9EBC880CAB78D /* Pods-NotificationService.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NotificationService.debug.xcconfig"; path = "Target Support Files/Pods-NotificationService/Pods-NotificationService.debug.xcconfig"; sourceTree = ""; }; + 95AD0663479892A2109EEFD0 /* Pods-ShareActionExtension.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ShareActionExtension.release.xcconfig"; path = "Target Support Files/Pods-ShareActionExtension/Pods-ShareActionExtension.release.xcconfig"; sourceTree = ""; }; 9776D7C4B79101CF70181127 /* Pods-NotificationService.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NotificationService.release.xcconfig"; path = "Target Support Files/Pods-NotificationService/Pods-NotificationService.release.xcconfig"; sourceTree = ""; }; 9780A4C98FFC65B32B50D1C0 /* Pods-MastodonTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MastodonTests.release.xcconfig"; path = "Target Support Files/Pods-MastodonTests/Pods-MastodonTests.release.xcconfig"; sourceTree = ""; }; 9A0982D8F349244EB558CDFD /* Pods-AppShared.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AppShared.debug.xcconfig"; path = "Target Support Files/Pods-AppShared/Pods-AppShared.debug.xcconfig"; sourceTree = ""; }; 9CFF58FD900AC059428700E7 /* Pods-NotificationService.asdk - release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NotificationService.asdk - release.xcconfig"; path = "Target Support Files/Pods-NotificationService/Pods-NotificationService.asdk - release.xcconfig"; sourceTree = ""; }; + A32B0CACBF35F4CC3CFAA043 /* Pods_ShareActionExtension.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ShareActionExtension.framework; sourceTree = BUILT_PRODUCTS_DIR; }; A4ABE34829701A4496C5BB64 /* Pods_Mastodon.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Mastodon.framework; sourceTree = BUILT_PRODUCTS_DIR; }; A67FD038ECDA0E411AF8DB4D /* Pods-Mastodon-MastodonUITests.asdk.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Mastodon-MastodonUITests.asdk.xcconfig"; path = "Target Support Files/Pods-Mastodon-MastodonUITests/Pods-Mastodon-MastodonUITests.asdk.xcconfig"; sourceTree = ""; }; A9B1FB898DFD6063B044298C /* Pods-AppShared.asdk - debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AppShared.asdk - debug.xcconfig"; path = "Target Support Files/Pods-AppShared/Pods-AppShared.asdk - debug.xcconfig"; sourceTree = ""; }; @@ -864,7 +893,6 @@ DB029E94266A20430062874E /* MastodonAuthenticationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonAuthenticationController.swift; sourceTree = ""; }; DB02CDAA26256A9500D0A2AF /* ThreadReplyLoaderTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadReplyLoaderTableViewCell.swift; sourceTree = ""; }; DB02CDBE2625AE5000D0A2AF /* AdaptiveUserInterfaceStyleBarButtonItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdaptiveUserInterfaceStyleBarButtonItem.swift; sourceTree = ""; }; - DB03F7EF26899097007B274C /* ComposeStatusContentTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeStatusContentTableViewCell.swift; sourceTree = ""; }; DB03F7F22689AEA3007B274C /* ComposeRepliedToStatusContentTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeRepliedToStatusContentTableViewCell.swift; sourceTree = ""; }; DB03F7F42689B782007B274C /* ComposeTableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeTableView.swift; sourceTree = ""; }; DB040ED026538E3C00BEE9D8 /* Trie.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Trie.swift; sourceTree = ""; }; @@ -874,7 +902,6 @@ DB0F814E264CFFD300F2A12B /* ar */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ar; path = ar.lproj/InfoPlist.strings; sourceTree = ""; }; DB0F814F264D1E2500F2A12B /* PickServerLoaderTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PickServerLoaderTableViewCell.swift; sourceTree = ""; }; DB118A8125E4B6E600FAB162 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; - DB118A8B25E4BFB500FAB162 /* HighlightDimmableButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HighlightDimmableButton.swift; sourceTree = ""; }; DB1D186B25EF5BA7003F1F23 /* PollTableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollTableView.swift; sourceTree = ""; }; DB1D842B26551A1C000346B3 /* StatusProvider+StatusTableViewKeyCommandNavigateable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StatusProvider+StatusTableViewKeyCommandNavigateable.swift"; sourceTree = ""; }; DB1D842D26552C4D000346B3 /* StatusTableViewControllerNavigateable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusTableViewControllerNavigateable.swift; sourceTree = ""; }; @@ -884,8 +911,6 @@ DB1D84372657B275000346B3 /* SegmentedControlNavigateable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SegmentedControlNavigateable.swift; sourceTree = ""; }; DB1E346725F518E20079D7DF /* CategoryPickerSection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CategoryPickerSection.swift; sourceTree = ""; }; DB1E347725F519300079D7DF /* PickServerItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PickServerItem.swift; sourceTree = ""; }; - DB1EE7AD267F3071000CC337 /* MastodonStatusContent+ParseResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MastodonStatusContent+ParseResult.swift"; sourceTree = ""; }; - DB1EE7AF267F3088000CC337 /* MastodonStatusContent+Appearance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MastodonStatusContent+Appearance.swift"; sourceTree = ""; }; DB1EE7B1267F9525000CC337 /* StatusProvider+StatusNodeDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StatusProvider+StatusNodeDelegate.swift"; sourceTree = ""; }; DB1FD43525F26899004CFCFC /* MastodonPickServerViewModel+LoadIndexedServerState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MastodonPickServerViewModel+LoadIndexedServerState.swift"; sourceTree = ""; }; DB1FD44325F26CCC004CFCFC /* PickServerSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PickServerSection.swift; sourceTree = ""; }; @@ -895,19 +920,16 @@ DB221B15260C395900AEFE46 /* CustomEmojiPickerInputViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomEmojiPickerInputViewModel.swift; sourceTree = ""; }; DB297B1A2679FAE200704C90 /* PlaceholderImageCacheService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaceholderImageCacheService.swift; sourceTree = ""; }; DB2B3ABD25E37E15007045F9 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; - DB2B3AE825E38850007045F9 /* UIViewPreview.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIViewPreview.swift; sourceTree = ""; }; DB2F073325E8ECF000957B2D /* AuthenticationViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthenticationViewModel.swift; sourceTree = ""; }; DB2FF50F260B113300ADA9FE /* ComposeStatusPollExpiresOptionCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeStatusPollExpiresOptionCollectionViewCell.swift; sourceTree = ""; }; DB35FC1E2612F1D9006193C9 /* ProfileRelationshipActionButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileRelationshipActionButton.swift; sourceTree = ""; }; DB35FC242612FD7A006193C9 /* ProfileFieldView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileFieldView.swift; sourceTree = ""; }; - DB35FC2E26130172006193C9 /* MastodonField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonField.swift; sourceTree = ""; }; DB36679C268AB91B0027D07F /* ComposeStatusAttachmentTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeStatusAttachmentTableViewCell.swift; sourceTree = ""; }; DB36679E268ABAF20027D07F /* ComposeStatusAttachmentSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeStatusAttachmentSection.swift; sourceTree = ""; }; DB3667A0268ABB2E0027D07F /* ComposeStatusAttachmentItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeStatusAttachmentItem.swift; sourceTree = ""; }; DB3667A3268AE2370027D07F /* ComposeStatusPollTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeStatusPollTableViewCell.swift; sourceTree = ""; }; DB3667A5268AE2620027D07F /* ComposeStatusPollSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeStatusPollSection.swift; sourceTree = ""; }; DB3667A7268AE2900027D07F /* ComposeStatusPollItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeStatusPollItem.swift; sourceTree = ""; }; - DB3667C9268B14A80027D07F /* ReplicaStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReplicaStatusView.swift; sourceTree = ""; }; DB3D0FED25BAA42200EAA174 /* MastodonSDK */ = {isa = PBXFileReference; lastKnownFileType = folder; path = MastodonSDK; sourceTree = ""; }; DB3D100E25BAA75E00EAA174 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; DB427DD225BAA00100D1B89D /* Mastodon.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Mastodon.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -935,7 +957,6 @@ DB4481B825EE289600BEFB67 /* UITableView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UITableView.swift; sourceTree = ""; }; DB4481C525EE2ADA00BEFB67 /* PollSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollSection.swift; sourceTree = ""; }; DB4481CB25EE2AFE00BEFB67 /* PollItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollItem.swift; sourceTree = ""; }; - DB4563BC25E11A24004DA0B9 /* KeyboardResponderService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyboardResponderService.swift; sourceTree = ""; }; DB45FAB525CA5485005A8AC7 /* UIAlertController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIAlertController.swift; sourceTree = ""; }; DB45FAD625CA6C76005A8AC7 /* UIBarButtonItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIBarButtonItem.swift; sourceTree = ""; }; DB45FADC25CA6F6B005A8AC7 /* APIService+CoreData+MastodonUser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+CoreData+MastodonUser.swift"; sourceTree = ""; }; @@ -966,12 +987,9 @@ DB4FFC29269EC39600D62E92 /* SearchToSearchDetailViewControllerAnimatedTransitioning.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SearchToSearchDetailViewControllerAnimatedTransitioning.swift; sourceTree = ""; }; DB4FFC2A269EC39600D62E92 /* SearchTransitionController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SearchTransitionController.swift; sourceTree = ""; }; DB5086A425CC0B7000C2C187 /* AvatarBarButtonItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AvatarBarButtonItem.swift; sourceTree = ""; }; - DB5086AA25CC0BBB00C2C187 /* AvatarConfigurableView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AvatarConfigurableView.swift; sourceTree = ""; }; - DB5086BD25CC0D9900C2C187 /* SplashPreference.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SplashPreference.swift; sourceTree = ""; }; DB51D170262832380062B7A1 /* BlurHashDecode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlurHashDecode.swift; sourceTree = ""; }; DB51D171262832380062B7A1 /* BlurHashEncode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlurHashEncode.swift; sourceTree = ""; }; DB52D33926839DD800D43133 /* ImageTask.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageTask.swift; sourceTree = ""; }; - DB55D32F25FB630A0002F825 /* TwitterTextEditor+String.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "TwitterTextEditor+String.swift"; sourceTree = ""; }; DB564BCF269F2F83001E39A7 /* ar */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = ar; path = ar.lproj/Localizable.stringsdict; sourceTree = ""; }; DB564BD1269F2F8A001E39A7 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = en; path = en.lproj/Localizable.stringsdict; sourceTree = ""; }; DB564BD2269F3B35001E39A7 /* StatusFilterService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusFilterService.swift; sourceTree = ""; }; @@ -993,7 +1011,7 @@ DB6180F726391D660018D199 /* MediaPreviewingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaPreviewingViewController.swift; sourceTree = ""; }; DB6180F926391F2E0018D199 /* MediaPreviewViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaPreviewViewModel.swift; sourceTree = ""; }; DB63BE7E268DD1070011D3F9 /* NotificationViewController+StatusProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NotificationViewController+StatusProvider.swift"; sourceTree = ""; }; - DB66728B25F9F8DC00D60309 /* ComposeViewModel+Diffable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ComposeViewModel+Diffable.swift"; sourceTree = ""; }; + DB66728B25F9F8DC00D60309 /* ComposeViewModel+DataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ComposeViewModel+DataSource.swift"; sourceTree = ""; }; DB66729525F9F91600D60309 /* ComposeStatusSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeStatusSection.swift; sourceTree = ""; }; DB66729B25F9F91F00D60309 /* ComposeStatusItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeStatusItem.swift; sourceTree = ""; }; DB68045A2636DC6A00430867 /* MastodonNotification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonNotification.swift; sourceTree = ""; }; @@ -1011,7 +1029,6 @@ DB6B35172601FA3400DC1E11 /* MastodonAttachmentService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonAttachmentService.swift; sourceTree = ""; }; DB6B351D2601FAEE00DC1E11 /* ComposeStatusAttachmentCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeStatusAttachmentCollectionViewCell.swift; sourceTree = ""; }; DB6C8C0E25F0A6AE00AAA452 /* Mastodon+Entity+Error.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Mastodon+Entity+Error.swift"; sourceTree = ""; }; - DB6D1B23263684C600ACB481 /* UserDefaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDefaults.swift; sourceTree = ""; }; DB6D1B3C2636857500ACB481 /* AppearancePreference.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppearancePreference.swift; sourceTree = ""; }; DB6D1B43263691CF00ACB481 /* Mastodon+API+Subscriptions+Policy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Mastodon+API+Subscriptions+Policy.swift"; sourceTree = ""; }; DB6D9F3426351B7A008423CD /* NotificationService+Decrypt.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NotificationService+Decrypt.swift"; sourceTree = ""; }; @@ -1024,7 +1041,6 @@ DB6D9F7C26358ED4008423CD /* SettingsSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsSection.swift; sourceTree = ""; }; DB6D9F8326358EEC008423CD /* SettingsItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsItem.swift; sourceTree = ""; }; DB6D9F9626367249008423CD /* SettingsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsViewController.swift; sourceTree = ""; }; - DB6F5E2E264E5518009108F4 /* MastodonRegex.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonRegex.swift; sourceTree = ""; }; DB6F5E34264E78E7009108F4 /* AutoCompleteViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoCompleteViewController.swift; sourceTree = ""; }; DB6F5E37264E994A009108F4 /* AutoCompleteTopChevronView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoCompleteTopChevronView.swift; sourceTree = ""; }; DB71FD2B25F86A5100512AE1 /* AvatarStackContainerButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AvatarStackContainerButton.swift; sourceTree = ""; }; @@ -1087,7 +1103,6 @@ DB98338525C945ED00AD9700 /* Strings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Strings.swift; sourceTree = ""; }; DB98338625C945ED00AD9700 /* Assets.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Assets.swift; sourceTree = ""; }; DB98339B25C96DE600AD9700 /* APIService+Account.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Account.swift"; sourceTree = ""; }; - DB9A485B2603010E008B817C /* PHPickerResultLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PHPickerResultLoader.swift; sourceTree = ""; }; DB9A486B26032AC1008B817C /* AttachmentContainerView+EmptyStateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AttachmentContainerView+EmptyStateView.swift"; sourceTree = ""; }; DB9A488926034D40008B817C /* ComposeViewModel+PublishState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ComposeViewModel+PublishState.swift"; sourceTree = ""; }; DB9A488F26035963008B817C /* APIService+Media.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Media.swift"; sourceTree = ""; }; @@ -1130,7 +1145,6 @@ DBAE3F8D2616E0B1004B8251 /* APIService+Block.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Block.swift"; sourceTree = ""; }; DBAE3F932616E28B004B8251 /* APIService+Follow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Follow.swift"; sourceTree = ""; }; DBAE3F9D2616E308004B8251 /* APIService+Mute.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Mute.swift"; sourceTree = ""; }; - DBAE3FA82617106E004B8251 /* MastodonMetricFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonMetricFormatter.swift; sourceTree = ""; }; DBAE3FAE26172FC0004B8251 /* RemoteProfileViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteProfileViewModel.swift; sourceTree = ""; }; DBAEDE5B267A058D00D25FF5 /* BlurhashImageCacheService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlurhashImageCacheService.swift; sourceTree = ""; }; DBAEDE60267B342D00D25FF5 /* StatusContentCacheService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusContentCacheService.swift; sourceTree = ""; }; @@ -1147,6 +1161,21 @@ DBB5256D2612D5A1002F1F29 /* ProfileStatusDashboardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileStatusDashboardView.swift; sourceTree = ""; }; DBB525842612D6DD002F1F29 /* ProfileStatusDashboardMeterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileStatusDashboardMeterView.swift; sourceTree = ""; }; DBB9759B262462E1004620BD /* ThreadMetaView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadMetaView.swift; sourceTree = ""; }; + DBBC24A726A52F9000398BB9 /* ComposeToolbarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeToolbarView.swift; sourceTree = ""; }; + DBBC24AB26A53D9300398BB9 /* ComposeStatusContentTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeStatusContentTableViewCell.swift; sourceTree = ""; }; + DBBC24AD26A53DC100398BB9 /* ReplicaStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReplicaStatusView.swift; sourceTree = ""; }; + DBBC24B426A540AE00398BB9 /* AvatarConfigurableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AvatarConfigurableView.swift; sourceTree = ""; }; + DBBC24BB26A542F500398BB9 /* ThemeService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeService.swift; sourceTree = ""; }; + DBBC24BE26A5443100398BB9 /* MastodonTheme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonTheme.swift; sourceTree = ""; }; + DBBC24BF26A5443100398BB9 /* SystemTheme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SystemTheme.swift; sourceTree = ""; }; + DBBC24C326A544B900398BB9 /* Theme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Theme.swift; sourceTree = ""; }; + DBBC24CE26A547AE00398BB9 /* ThemeService+Appearance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ThemeService+Appearance.swift"; sourceTree = ""; }; + DBBC24D626A54BCB00398BB9 /* MastodonRegex.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MastodonRegex.swift; sourceTree = ""; }; + DBBC24D726A54BCB00398BB9 /* MastodonStatusContent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MastodonStatusContent.swift; sourceTree = ""; }; + DBBC24D826A54BCB00398BB9 /* MastodonMetricFormatter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MastodonMetricFormatter.swift; sourceTree = ""; }; + DBBC24D926A54BCB00398BB9 /* MastodonStatusContent+ParseResult.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "MastodonStatusContent+ParseResult.swift"; sourceTree = ""; }; + DBBC24DA26A54BCB00398BB9 /* MastodonField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MastodonField.swift; sourceTree = ""; }; + DBBC24DB26A54BCB00398BB9 /* MastodonStatusContent+Appearance.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "MastodonStatusContent+Appearance.swift"; sourceTree = ""; }; DBBE1B4425F3474B0081417A /* MastodonPickServerAppearance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonPickServerAppearance.swift; sourceTree = ""; }; DBBF1DBE2652401B00E5B703 /* AutoCompleteViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoCompleteViewModel.swift; sourceTree = ""; }; DBBF1DC126524D2900E5B703 /* AutoCompleteTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoCompleteTableViewCell.swift; sourceTree = ""; }; @@ -1180,11 +1209,7 @@ DBCC3B9426157E6E0045B23D /* APIService+Relationship.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Relationship.swift"; sourceTree = ""; }; DBCC3B9A2615849F0045B23D /* PrivateNote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivateNote.swift; sourceTree = ""; }; DBCCC71D25F73297007E1AB6 /* APIService+Reblog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Reblog.swift"; sourceTree = ""; }; - DBD376A62692EA00007FEC24 /* ThemeService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeService.swift; sourceTree = ""; }; - DBD376A92692EA4F007FEC24 /* Theme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Theme.swift; sourceTree = ""; }; DBD376AB2692ECDB007FEC24 /* ThemePreference.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemePreference.swift; sourceTree = ""; }; - DBD376AD2692EE0A007FEC24 /* MastodonTheme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonTheme.swift; sourceTree = ""; }; - DBD376AF2692F20F007FEC24 /* SystemTheme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SystemTheme.swift; sourceTree = ""; }; DBD376B1269302A4007FEC24 /* UITableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITableViewCell.swift; sourceTree = ""; }; DBD9148F25DF6D8D00903DFD /* APIService+Onboarding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Onboarding.swift"; sourceTree = ""; }; DBE0821425CD382600FD6BBD /* MastodonRegisterViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonRegisterViewController.swift; sourceTree = ""; }; @@ -1209,6 +1234,14 @@ DBF96325262EC0A6001D8D25 /* AuthenticationServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AuthenticationServices.framework; path = System/Library/Frameworks/AuthenticationServices.framework; sourceTree = SDKROOT; }; DBF98149265E24F500E4BA07 /* ProfileFieldCollectionViewHeaderFooterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileFieldCollectionViewHeaderFooterView.swift; sourceTree = ""; }; DBF9814B265E339500E4BA07 /* ProfileFieldAddEntryCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileFieldAddEntryCollectionViewCell.swift; sourceTree = ""; }; + DBFEF05526A576EE006D7ED1 /* StatusEditorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusEditorView.swift; sourceTree = ""; }; + DBFEF05626A576EE006D7ED1 /* ComposeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeView.swift; sourceTree = ""; }; + DBFEF05726A576EE006D7ED1 /* ComposeViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeViewModel.swift; sourceTree = ""; }; + DBFEF05826A576EE006D7ED1 /* ContentWarningEditorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentWarningEditorView.swift; sourceTree = ""; }; + DBFEF05926A576EE006D7ED1 /* StatusAuthorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusAuthorView.swift; sourceTree = ""; }; + DBFEF05A26A576EE006D7ED1 /* StatusAttachmentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusAttachmentView.swift; sourceTree = ""; }; + DBFEF06226A577F2006D7ED1 /* StatusAttachmentViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusAttachmentViewModel.swift; sourceTree = ""; }; + DBFEF06726A58D07006D7ED1 /* ShareActionExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = ShareActionExtension.entitlements; sourceTree = ""; }; DDB1B139FA8EA26F510D58B6 /* Pods-AppShared.asdk - release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AppShared.asdk - release.xcconfig"; path = "Target Support Files/Pods-AppShared/Pods-AppShared.asdk - release.xcconfig"; sourceTree = ""; }; E5C7236E58D14A0322FE00F2 /* Pods-Mastodon-MastodonUITests.asdk - debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Mastodon-MastodonUITests.asdk - debug.xcconfig"; path = "Target Support Files/Pods-Mastodon-MastodonUITests/Pods-Mastodon-MastodonUITests.asdk - debug.xcconfig"; sourceTree = ""; }; EC6E707B68A67DB08EC288FA /* Pods-MastodonTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MastodonTests.debug.xcconfig"; path = "Target Support Files/Pods-MastodonTests/Pods-MastodonTests.debug.xcconfig"; sourceTree = ""; }; @@ -1296,9 +1329,18 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + DBBC24B826A5421800398BB9 /* CommonOSLog in Frameworks */, DBC6462526A1720B00B0E31B /* MastodonUI in Frameworks */, DBC6463726A195DB00B0E31B /* CoreDataStack.framework in Frameworks */, + DB41ED8426A54D8A00F58330 /* MetaTextView in Frameworks */, + DB41ED8226A54D8A00F58330 /* MastodonMeta in Frameworks */, + DB41ED7E26A54D6D00F58330 /* Fuzi in Frameworks */, + DBBC24D126A5484F00398BB9 /* UITextView+Placeholder in Frameworks */, + DBBC24B226A53ED200398BB9 /* ActiveLabel in Frameworks */, + DBBC24AA26A5301B00398BB9 /* MastodonSDK in Frameworks */, + DB41ED8026A54D7C00F58330 /* AlamofireImage in Frameworks */, DBC6463326A195DB00B0E31B /* AppShared.framework in Frameworks */, + 4278334D6033AEEE0A1C5155 /* Pods_ShareActionExtension.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1306,6 +1348,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + DBBC24CD26A5471E00398BB9 /* MastodonExtension in Frameworks */, DB00CA972632DDB600A54956 /* CommonOSLog in Frameworks */, DB6D9F42263527CE008423CD /* AlamofireImage in Frameworks */, DB6804A52637CDCC00430867 /* AppShared.framework in Frameworks */, @@ -1415,6 +1458,10 @@ 46DAB0EBDDFB678347CD96FF /* Pods-MastodonTests.asdk - release.xcconfig */, 3B7FD8F28DDA8FBCE5562B78 /* Pods-NotificationService.asdk - debug.xcconfig */, 9CFF58FD900AC059428700E7 /* Pods-NotificationService.asdk - release.xcconfig */, + 77EE917BC055E6621C0452B6 /* Pods-ShareActionExtension.debug.xcconfig */, + 6130CBE4B26E3C976ACC1688 /* Pods-ShareActionExtension.asdk - debug.xcconfig */, + 5CE45680252519F42FEA2D13 /* Pods-ShareActionExtension.asdk - release.xcconfig */, + 95AD0663479892A2109EEFD0 /* Pods-ShareActionExtension.release.xcconfig */, ); path = Pods; sourceTree = ""; @@ -1505,7 +1552,6 @@ children = ( DB5086A425CC0B7000C2C187 /* AvatarBarButtonItem.swift */, 2D42FF8425C8224F004A627A /* HitTestExpandedButton.swift */, - DB118A8B25E4BFB500FAB162 /* HighlightDimmableButton.swift */, 0FAA101125E105390017CCDE /* PrimaryActionButton.swift */, ); path = Button; @@ -1532,9 +1578,6 @@ isa = PBXGroup; children = ( 2D5A3D0225CF8742002347D6 /* ControlContainableScrollViews.swift */, - DB2B3AE825E38850007045F9 /* UIViewPreview.swift */, - DB55D32F25FB630A0002F825 /* TwitterTextEditor+String.swift */, - DB9A485B2603010E008B817C /* PHPickerResultLoader.swift */, DB51D170262832380062B7A1 /* BlurHashDecode.swift */, DB51D171262832380062B7A1 /* BlurHashEncode.swift */, DB6180EC26391C6C0018D199 /* TransitioningMath.swift */, @@ -1549,10 +1592,9 @@ children = ( DB45FB0425CA87B4005A8AC7 /* APIService */, DB49A61925FF327D00B98345 /* EmojiService */, - DBD376A82692EA3F007FEC24 /* ThemeService */, DB9A489B26036E19008B817C /* MastodonAttachmentService */, + DBBC24BD26A5441A00398BB9 /* ThemeService */, DB45FB0E25CA87D0005A8AC7 /* AuthenticationService.swift */, - DB4563BC25E11A24004DA0B9 /* KeyboardResponderService.swift */, 2D206B8B25F6015000143C56 /* AudioPlaybackService.swift */, 2DA6054625F716A2006356F9 /* PlaybackState.swift */, 5DF1054025F886D400D6C0D4 /* VideoPlaybackService.swift */, @@ -1585,7 +1627,7 @@ children = ( 2D38F1FC25CD47D900561493 /* StatusProvider */, DBAE3F742615DD63004B8251 /* UserProvider */, - DB5086AA25CC0BBB00C2C187 /* AvatarConfigurableView.swift */, + DBBC24B426A540AE00398BB9 /* AvatarConfigurableView.swift */, 2D69CFF325CA9E2200C3A1B2 /* LoadMoreConfigurableTableViewContainer.swift */, 2D5A3D3725CF8D9F002347D6 /* ScrollViewContainer.swift */, DB59F10325EF5EBC001F1DAB /* TableViewCellHeightCacheableContainer.swift */, @@ -1765,6 +1807,7 @@ 452147B2903DF38070FE56A2 /* Pods_MastodonTests.framework */, F4A2A2D7000E477CA459ADA9 /* Pods_AppShared.framework */, 374AA339A20E0FAC75BCDA6D /* Pods_NotificationService.framework */, + A32B0CACBF35F4CC3CFAA043 /* Pods_ShareActionExtension.framework */, ); name = Frameworks; sourceTree = ""; @@ -1850,7 +1893,6 @@ isa = PBXGroup; children = ( DB03F7F22689AEA3007B274C /* ComposeRepliedToStatusContentTableViewCell.swift */, - DB03F7EF26899097007B274C /* ComposeStatusContentTableViewCell.swift */, DB36679C268AB91B0027D07F /* ComposeStatusAttachmentTableViewCell.swift */, DB3667A3268AE2370027D07F /* ComposeStatusPollTableViewCell.swift */, ); @@ -1961,10 +2003,10 @@ 2D61335525C1886800CAE157 /* Service */, DB8AF55525C1379F002E6C99 /* Scene */, DB8AF54125C13647002E6C99 /* Coordinator */, - DB9E0D6925EDFFE500CFDD76 /* Helper */, DB8AF56225C138BC002E6C99 /* Extension */, 2D5A3D0125CF8640002347D6 /* Vender */, DB73B495261F030D002E9E9F /* Activity */, + DBBC24D526A54BCB00398BB9 /* Helper */, DB5086CB25CC0DB400C2C187 /* Preference */, 2D69CFF225CA9E2200C3A1B2 /* Protocol */, DB98338425C945ED00AD9700 /* Generated */, @@ -2130,7 +2172,6 @@ DB5086CB25CC0DB400C2C187 /* Preference */ = { isa = PBXGroup; children = ( - DB5086BD25CC0D9900C2C187 /* SplashPreference.swift */, DBA465942696E387002B41DB /* AppPreference.swift */, DB6D1B3C2636857500ACB481 /* AppearancePreference.swift */, DBE54AC52636C89F004E7C0B /* NotificationPreference.swift */, @@ -2144,7 +2185,7 @@ DB55D32225FB4D320002F825 /* View */ = { isa = PBXGroup; children = ( - DB3667C9268B14A80027D07F /* ReplicaStatusView.swift */, + DBBC24AD26A53DC100398BB9 /* ReplicaStatusView.swift */, DB03F7F42689B782007B274C /* ComposeTableView.swift */, DBA0A11225FB3FC10079C110 /* ComposeToolbarView.swift */, DB8190C52601FF0400020C08 /* AttachmentContainerView.swift */, @@ -2291,7 +2332,7 @@ DB03F7F1268990A2007B274C /* TableViewCell */, DB789A0A25F9F2950071ACA0 /* ComposeViewController.swift */, DB789A1125F9F2CC0071ACA0 /* ComposeViewModel.swift */, - DB66728B25F9F8DC00D60309 /* ComposeViewModel+Diffable.swift */, + DB66728B25F9F8DC00D60309 /* ComposeViewModel+DataSource.swift */, DB9A488926034D40008B817C /* ComposeViewModel+PublishState.swift */, ); path = Compose; @@ -2300,6 +2341,7 @@ DB789A2125F9F76D0071ACA0 /* CollectionViewCell */ = { isa = PBXGroup; children = ( + DBBC24AB26A53D9300398BB9 /* ComposeStatusContentTableViewCell.swift */, DB6B351D2601FAEE00DC1E11 /* ComposeStatusAttachmentCollectionViewCell.swift */, DB87D4442609BE0500D12C0D /* ComposeStatusPollOptionCollectionViewCell.swift */, DB87D4502609CF1E00D12C0D /* ComposeStatusPollOptionAppendEntryCollectionViewCell.swift */, @@ -2445,7 +2487,6 @@ DB6C8C0525F0921200AAA452 /* MastodonSDK */, DB44384E25E8C1FA008912A2 /* CALayer.swift */, 2DF123A625C3B0210020F248 /* ActiveLabel.swift */, - 2D46975D25C2A54100CF4AA9 /* NSLayoutConstraint.swift */, DB68586325E619B700F0A850 /* NSKeyValueObservation.swift */, DB0140CE25C42AEE00F9F3CF /* OSLog.swift */, DB68A06225E905E000CFDF14 /* UIApplication.swift */, @@ -2472,7 +2513,6 @@ 0F20223826146553000C64BF /* Array.swift */, DBCC3B2F261440A50045B23D /* UITabBarController.swift */, DBCC3B35261440BA0045B23D /* UINavigationController.swift */, - DB6D1B23263684C600ACB481 /* UserDefaults.swift */, DB97131E2666078B00BD1E90 /* Date.swift */, DBAC6489267DC355007FE9FD /* NSDiffableDataSourceSnapshot.swift */, DB52D33926839DD800D43133 /* ImageTask.swift */, @@ -2584,19 +2624,6 @@ path = ViewModel; sourceTree = ""; }; - DB9E0D6925EDFFE500CFDD76 /* Helper */ = { - isa = PBXGroup; - children = ( - 2D42FF6A25C817D2004A627A /* MastodonStatusContent.swift */, - DB1EE7AD267F3071000CC337 /* MastodonStatusContent+ParseResult.swift */, - DB1EE7AF267F3088000CC337 /* MastodonStatusContent+Appearance.swift */, - DB6F5E2E264E5518009108F4 /* MastodonRegex.swift */, - DB35FC2E26130172006193C9 /* MastodonField.swift */, - DBAE3FA82617106E004B8251 /* MastodonMetricFormatter.swift */, - ); - path = Helper; - sourceTree = ""; - }; DBA5E7A6263BD298004598BB /* ContextMenu */ = { isa = PBXGroup; children = ( @@ -2716,6 +2743,31 @@ path = View; sourceTree = ""; }; + DBBC24BD26A5441A00398BB9 /* ThemeService */ = { + isa = PBXGroup; + children = ( + DBBC24C326A544B900398BB9 /* Theme.swift */, + DBBC24BE26A5443100398BB9 /* MastodonTheme.swift */, + DBBC24BF26A5443100398BB9 /* SystemTheme.swift */, + DBBC24BB26A542F500398BB9 /* ThemeService.swift */, + DBBC24CE26A547AE00398BB9 /* ThemeService+Appearance.swift */, + ); + path = ThemeService; + sourceTree = ""; + }; + DBBC24D526A54BCB00398BB9 /* Helper */ = { + isa = PBXGroup; + children = ( + DBBC24D626A54BCB00398BB9 /* MastodonRegex.swift */, + DBBC24D726A54BCB00398BB9 /* MastodonStatusContent.swift */, + DBBC24D826A54BCB00398BB9 /* MastodonMetricFormatter.swift */, + DBBC24D926A54BCB00398BB9 /* MastodonStatusContent+ParseResult.swift */, + DBBC24DA26A54BCB00398BB9 /* MastodonField.swift */, + DBBC24DB26A54BCB00398BB9 /* MastodonStatusContent+Appearance.swift */, + ); + path = Helper; + sourceTree = ""; + }; DBBF1DC02652402000E5B703 /* View */ = { isa = PBXGroup; children = ( @@ -2735,10 +2787,10 @@ DBC6461326A170AB00B0E31B /* ShareActionExtension */ = { isa = PBXGroup; children = ( - DBC6462226A1712000B0E31B /* ShareViewModel.swift */, - DBC6461426A170AB00B0E31B /* ShareViewController.swift */, - DBC6461626A170AB00B0E31B /* MainInterface.storyboard */, + DBFEF06726A58D07006D7ED1 /* ShareActionExtension.entitlements */, DBC6461926A170AB00B0E31B /* Info.plist */, + DBC6461626A170AB00B0E31B /* MainInterface.storyboard */, + DBFEF06126A57721006D7ED1 /* Scene */, ); path = ShareActionExtension; sourceTree = ""; @@ -2769,17 +2821,6 @@ path = FetchedResultsController; sourceTree = ""; }; - DBD376A82692EA3F007FEC24 /* ThemeService */ = { - isa = PBXGroup; - children = ( - DBD376A62692EA00007FEC24 /* ThemeService.swift */, - DBD376A92692EA4F007FEC24 /* Theme.swift */, - DBD376AD2692EE0A007FEC24 /* MastodonTheme.swift */, - DBD376AF2692F20F007FEC24 /* SystemTheme.swift */, - ); - path = ThemeService; - sourceTree = ""; - }; DBE0821A25CD382900FD6BBD /* Register */ = { isa = PBXGroup; children = ( @@ -2850,6 +2891,31 @@ path = NotificationService; sourceTree = ""; }; + DBFEF05426A576EE006D7ED1 /* View */ = { + isa = PBXGroup; + children = ( + DBBC24A726A52F9000398BB9 /* ComposeToolbarView.swift */, + DBFEF05526A576EE006D7ED1 /* StatusEditorView.swift */, + DBFEF05626A576EE006D7ED1 /* ComposeView.swift */, + DBFEF05726A576EE006D7ED1 /* ComposeViewModel.swift */, + DBFEF05826A576EE006D7ED1 /* ContentWarningEditorView.swift */, + DBFEF05926A576EE006D7ED1 /* StatusAuthorView.swift */, + DBFEF05A26A576EE006D7ED1 /* StatusAttachmentView.swift */, + DBFEF06226A577F2006D7ED1 /* StatusAttachmentViewModel.swift */, + ); + path = View; + sourceTree = ""; + }; + DBFEF06126A57721006D7ED1 /* Scene */ = { + isa = PBXGroup; + children = ( + DBFEF05426A576EE006D7ED1 /* View */, + DBC6462226A1712000B0E31B /* ShareViewModel.swift */, + DBC6461426A170AB00B0E31B /* ShareViewController.swift */, + ); + path = Scene; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ @@ -3023,6 +3089,7 @@ isa = PBXNativeTarget; buildConfigurationList = DBC6462126A170AB00B0E31B /* Build configuration list for PBXNativeTarget "ShareActionExtension" */; buildPhases = ( + 641DEA63EE5B048C9A551302 /* [CP] Check Pods Manifest.lock */, DBC6460E26A170AB00B0E31B /* Sources */, DBC6460F26A170AB00B0E31B /* Frameworks */, DBC6461026A170AB00B0E31B /* Resources */, @@ -3036,6 +3103,14 @@ name = ShareActionExtension; packageProductDependencies = ( DBC6462426A1720B00B0E31B /* MastodonUI */, + DBBC24A926A5301B00398BB9 /* MastodonSDK */, + DBBC24B126A53ED200398BB9 /* ActiveLabel */, + DBBC24B726A5421800398BB9 /* CommonOSLog */, + DBBC24D026A5484F00398BB9 /* UITextView+Placeholder */, + DB41ED7D26A54D6D00F58330 /* Fuzi */, + DB41ED7F26A54D7C00F58330 /* AlamofireImage */, + DB41ED8126A54D8A00F58330 /* MastodonMeta */, + DB41ED8326A54D8A00F58330 /* MetaTextView */, ); productName = ShareActionExtension; productReference = DBC6461226A170AB00B0E31B /* ShareActionExtension.appex */; @@ -3059,6 +3134,7 @@ packageProductDependencies = ( DB00CA962632DDB600A54956 /* CommonOSLog */, DB6D9F41263527CE008423CD /* AlamofireImage */, + DBBC24CC26A5471E00398BB9 /* MastodonExtension */, ); productName = NotificationService; productReference = DBF8AE13263293E400C9C23C /* NotificationService.appex */; @@ -3262,6 +3338,28 @@ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Mastodon/Pods-Mastodon-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; + 641DEA63EE5B048C9A551302 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-ShareActionExtension-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; 6E033728B42BA1C0018B6131 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -3392,7 +3490,6 @@ buildActionMask = 2147483647; files = ( DBB525212611EBD6002F1F29 /* ProfilePagingViewController.swift in Sources */, - DB35FC2F26130172006193C9 /* MastodonField.swift in Sources */, DB6180EB26391C140018D199 /* MediaPreviewTransitionItem.swift in Sources */, DB98337125C9443200AD9700 /* APIService+Authentication.swift in Sources */, DBE3CDCF261C42ED00430CC6 /* TimelineHeaderView.swift in Sources */, @@ -3433,7 +3530,6 @@ DB1FD45A25F27898004CFCFC /* CategoryPickerItem.swift in Sources */, DB6180F626391D580018D199 /* MediaPreviewableViewController.swift in Sources */, 2D571B2F26004EC000540450 /* NavigationBarProgressView.swift in Sources */, - DBD376AA2692EA4F007FEC24 /* Theme.swift in Sources */, 0FAA101225E105390017CCDE /* PrimaryActionButton.swift in Sources */, DB443CD42694627B00159B29 /* AppearanceView.swift in Sources */, DBF1D24E269DAF5D00C1C08A /* SearchDetailViewController.swift in Sources */, @@ -3470,6 +3566,7 @@ DB297B1B2679FAE200704C90 /* PlaceholderImageCacheService.swift in Sources */, 2D8FCA082637EABB00137F46 /* APIService+FollowRequest.swift in Sources */, 2D152A8C25C295CC009AA50C /* StatusView.swift in Sources */, + DBBC24DE26A54BCB00398BB9 /* MastodonMetricFormatter.swift in Sources */, 2DE0FAC82615F5F000CDF649 /* SearchRecommendAccountsCollectionViewCell.swift in Sources */, DBF1D251269DB01200C1C08A /* SearchHistoryViewController.swift in Sources */, 2D42FF8525C8224F004A627A /* HitTestExpandedButton.swift in Sources */, @@ -3478,6 +3575,7 @@ 2D42FF8F25C8228A004A627A /* UIButton.swift in Sources */, DB789A0B25F9F2950071ACA0 /* ComposeViewController.swift in Sources */, DB938F0926240F3C00E5B6C1 /* RemoteThreadViewModel.swift in Sources */, + DBBC24AE26A53DC100398BB9 /* ReplicaStatusView.swift in Sources */, DB75BF1E263C1C1B00EDBF1F /* CustomScheduler.swift in Sources */, 0FAA102725E1126A0017CCDE /* MastodonPickServerViewController.swift in Sources */, DB59F0FE25EF5D96001F1DAB /* StatusProvider+UITableViewDelegate.swift in Sources */, @@ -3494,6 +3592,7 @@ DB2FF510260B113300ADA9FE /* ComposeStatusPollExpiresOptionCollectionViewCell.swift in Sources */, 0F202213261351F5000C64BF /* APIService+HashtagTimeline.swift in Sources */, DB0AC6FC25CD02E600D75117 /* APIService+Instance.swift in Sources */, + DBBC24DC26A54BCB00398BB9 /* MastodonRegex.swift in Sources */, 2D69D00A25CAA00300C3A1B2 /* APIService+CoreData+Status.swift in Sources */, DB4481C625EE2ADA00BEFB67 /* PollSection.swift in Sources */, DBAC648A267DC355007FE9FD /* NSDiffableDataSourceSnapshot.swift in Sources */, @@ -3532,10 +3631,10 @@ DBE3CDFB261C6CA500430CC6 /* FavoriteViewModel.swift in Sources */, DB8AF52F25C13561002E6C99 /* DocumentStore.swift in Sources */, 2D206B7225F5D27F00143C56 /* AudioContainerView.swift in Sources */, + DBBC24C126A5443100398BB9 /* SystemTheme.swift in Sources */, DB9D6C2425E502C60051B173 /* MosaicImageViewModel.swift in Sources */, DBE3CE01261D623D00430CC6 /* FavoriteViewModel+State.swift in Sources */, 2D82BA0525E7897700E36F0F /* MastodonResendEmailViewModelNavigationDelegateShim.swift in Sources */, - DB55D33025FB630A0002F825 /* TwitterTextEditor+String.swift in Sources */, 2D38F1EB25CD477000561493 /* HomeTimelineViewModel+LoadLatestState.swift in Sources */, 0F202201261326E6000C64BF /* HashtagTimelineViewModel.swift in Sources */, DB6D9F9726367249008423CD /* SettingsViewController.swift in Sources */, @@ -3559,7 +3658,7 @@ DB9A48962603685D008B817C /* MastodonAttachmentService+UploadState.swift in Sources */, 2D198643261BF09500F0B013 /* SearchResultItem.swift in Sources */, 2DAC9E38262FC2320062E1A6 /* SuggestionAccountViewController.swift in Sources */, - DB66728C25F9F8DC00D60309 /* ComposeViewModel+Diffable.swift in Sources */, + DB66728C25F9F8DC00D60309 /* ComposeViewModel+DataSource.swift in Sources */, DB6180E02639194B0018D199 /* MediaPreviewPagingViewController.swift in Sources */, DBE0822425CD3F1E00FD6BBD /* MastodonRegisterViewModel.swift in Sources */, 2DF75B9B25D0E27500694EC8 /* StatusProviderFacade.swift in Sources */, @@ -3591,13 +3690,13 @@ 2D206B9225F60EA700143C56 /* UIControl.swift in Sources */, 2D9DB96B263A91D1007C1D71 /* APIService+DomainBlock.swift in Sources */, DBBF1DC92652538500E5B703 /* AutoCompleteSection.swift in Sources */, - 2D46975E25C2A54100CF4AA9 /* NSLayoutConstraint.swift in Sources */, 2D45E5BF25C9549700A6D639 /* PublicTimelineViewModel+State.swift in Sources */, DB8AF55D25C138B7002E6C99 /* UIViewController.swift in Sources */, DB7F48452620241000796008 /* ProfileHeaderViewModel.swift in Sources */, 2D3F9E0425DFA133004262D9 /* UITapGestureRecognizer.swift in Sources */, 5DDDF1992617447F00311060 /* Mastodon+Entity+Tag.swift in Sources */, 5B90C45F262599800002E742 /* SettingsToggleTableViewCell.swift in Sources */, + DBBC24DF26A54BCB00398BB9 /* MastodonStatusContent+ParseResult.swift in Sources */, 2D694A7425F9EB4E0038ADDC /* ContentWarningOverlayView.swift in Sources */, DBAE3F682615DD60004B8251 /* UserProvider.swift in Sources */, DBAC6488267D388B007FE9FD /* ASTableNode.swift in Sources */, @@ -3607,12 +3706,11 @@ DB4481CC25EE2AFE00BEFB67 /* PollItem.swift in Sources */, DB44767B260B3B8C00B66B82 /* CustomEmojiPickerInputView.swift in Sources */, 0F20222D261457EE000C64BF /* HashtagTimelineViewModel+LoadOldestState.swift in Sources */, - DB4563BD25E11A24004DA0B9 /* KeyboardResponderService.swift in Sources */, - DB5086BE25CC0D9900C2C187 /* SplashPreference.swift in Sources */, 5B90C462262599800002E742 /* SettingsSectionHeader.swift in Sources */, DB44768B260B3F2100B66B82 /* CustomEmojiPickerItem.swift in Sources */, 2DF75BA125D0E29D00694EC8 /* StatusProvider+StatusTableViewCellDelegate.swift in Sources */, 5DF1056425F887CB00D6C0D4 /* AVPlayer.swift in Sources */, + DBBC24E126A54BCB00398BB9 /* MastodonStatusContent+Appearance.swift in Sources */, DBBF1DCB2652539E00E5B703 /* AutoCompleteItem.swift in Sources */, 2DA6054725F716A2006356F9 /* PlaybackState.swift in Sources */, DB35FC1F2612F1D9006193C9 /* ProfileRelationshipActionButton.swift in Sources */, @@ -3627,11 +3725,9 @@ DB1FD43625F26899004CFCFC /* MastodonPickServerViewModel+LoadIndexedServerState.swift in Sources */, 2D939AE825EE1CF80076FA61 /* MastodonRegisterViewController+Avatar.swift in Sources */, 2D084B8D26258EA3003AA3AF /* NotificationViewModel+Diffable.swift in Sources */, - DB6D1B24263684C600ACB481 /* UserDefaults.swift in Sources */, DB3667A1268ABB2E0027D07F /* ComposeStatusAttachmentItem.swift in Sources */, DB1D186C25EF5BA7003F1F23 /* PollTableView.swift in Sources */, DBA94434265CBB5300C537E1 /* ProfileFieldSection.swift in Sources */, - DB6F5E2F264E5518009108F4 /* MastodonRegex.swift in Sources */, DB023295267F0AB800031745 /* ASMetaEditableTextNode.swift in Sources */, 2D5981A125E4A593000FB903 /* MastodonConfirmEmailViewModel.swift in Sources */, DB4F096C269EFA2000D62E92 /* SearchResultViewController+StatusProvider.swift in Sources */, @@ -3650,7 +3746,9 @@ DBB525642612C988002F1F29 /* MeProfileViewModel.swift in Sources */, 5BB04FE9262EFC300043BFF6 /* ReportedStatusTableviewCell.swift in Sources */, DBAE3F822615DDA3004B8251 /* ProfileViewController+UserProvider.swift in Sources */, + DBBC24C426A544B900398BB9 /* Theme.swift in Sources */, DB938EED2623F79B00E5B6C1 /* ThreadViewModel.swift in Sources */, + DBBC24AC26A53D9300398BB9 /* ComposeStatusContentTableViewCell.swift in Sources */, DBC7A67C260DFADE00E57475 /* StatusPublishService.swift in Sources */, DBCBCC092680B01B000F5B51 /* AsyncHomeTimelineViewModel+LoadMiddleState.swift in Sources */, 2DCB73FD2615C13900EC03D4 /* SearchRecommendCollectionHeader.swift in Sources */, @@ -3663,6 +3761,7 @@ DB4F0963269ED06300D62E92 /* SearchResultViewController.swift in Sources */, DBBF1DC5265251C300E5B703 /* AutoCompleteViewModel+Diffable.swift in Sources */, DB68A04A25E9027700CFDF14 /* AdaptiveStatusBarStyleNavigationController.swift in Sources */, + DBBC24BC26A542F500398BB9 /* ThemeService.swift in Sources */, 0FB3D33825E6401400AAD544 /* PickServerCell.swift in Sources */, DB6D9F8426358EEC008423CD /* SettingsItem.swift in Sources */, 2D364F7825E66D8300204FDC /* MastodonResendEmailViewModel.swift in Sources */, @@ -3670,6 +3769,7 @@ DBA465932696B495002B41DB /* APIService+WebFinger.swift in Sources */, DB8AF54525C13647002E6C99 /* NeedsDependency.swift in Sources */, DB9D6BF825E4F5690051B173 /* NotificationViewController.swift in Sources */, + DBBC24DD26A54BCB00398BB9 /* MastodonStatusContent.swift in Sources */, 2DAC9E46262FC9FD0062E1A6 /* SuggestionAccountTableViewCell.swift in Sources */, DB4FFC2C269EC39600D62E92 /* SearchTransitionController.swift in Sources */, DBA5E7A9263BD3A4004598BB /* ContextMenuImagePreviewViewController.swift in Sources */, @@ -3689,7 +3789,6 @@ DB938F1526241FDF00E5B6C1 /* APIService+Thread.swift in Sources */, DB482A45261335BA008AE74C /* UserTimelineViewController+Provider.swift in Sources */, 2D206B8625F5FB0900143C56 /* Double.swift in Sources */, - DB9A485C2603010E008B817C /* PHPickerResultLoader.swift in Sources */, 2D76319F25C1521200929FB9 /* StatusSection.swift in Sources */, DB35FC252612FD7A006193C9 /* ProfileFieldView.swift in Sources */, DB938F0326240EA300E5B6C1 /* CachedThreadViewModel.swift in Sources */, @@ -3697,8 +3796,6 @@ 2D650FAB25ECDC9300851B58 /* Mastodon+Entity+Error+Detail.swift in Sources */, 2D24E12D2626FD2E00A59D4F /* NotificationViewModel+LoadOldestState.swift in Sources */, 2DB72C8C262D764300CE6173 /* Mastodon+Entity+Notification+Type.swift in Sources */, - DB118A8C25E4BFB500FAB162 /* HighlightDimmableButton.swift in Sources */, - DBAE3FA92617106E004B8251 /* MastodonMetricFormatter.swift in Sources */, 2D35237A26256D920031AF25 /* NotificationSection.swift in Sources */, DB084B5725CBC56C00F898ED /* Status.swift in Sources */, DBCBCC072680AFEC000F5B51 /* AsyncHomeTimelineViewModel+LoadLatestState.swift in Sources */, @@ -3707,7 +3804,6 @@ DB9282B225F3222800823B15 /* PickServerEmptyStateView.swift in Sources */, DB1FD45025F26FA1004CFCFC /* MastodonPickServerViewModel+Diffable.swift in Sources */, DBD376AC2692ECDB007FEC24 /* ThemePreference.swift in Sources */, - DB2B3AE925E38850007045F9 /* UIViewPreview.swift in Sources */, DB4F097D26A03A5B00D62E92 /* SearchHistoryItem.swift in Sources */, DB68046C2636DC9E00430867 /* MastodonNotification.swift in Sources */, DBAE3F9E2616E308004B8251 /* APIService+Mute.swift in Sources */, @@ -3719,12 +3815,12 @@ DBBE1B4525F3474B0081417A /* MastodonPickServerAppearance.swift in Sources */, DB98338725C945ED00AD9700 /* Strings.swift in Sources */, 2D7867192625B77500211898 /* NotificationItem.swift in Sources */, - DB1EE7AE267F3071000CC337 /* MastodonStatusContent+ParseResult.swift in Sources */, DB45FAB625CA5485005A8AC7 /* UIAlertController.swift in Sources */, DBE0821525CD382600FD6BBD /* MastodonRegisterViewController.swift in Sources */, 2D5A3D0325CF8742002347D6 /* ControlContainableScrollViews.swift in Sources */, DB36679D268AB91B0027D07F /* ComposeStatusAttachmentTableViewCell.swift in Sources */, DB98336B25C9420100AD9700 /* APIService+App.swift in Sources */, + DBBC24E026A54BCB00398BB9 /* MastodonField.swift in Sources */, DBCBCC032680AF6E000F5B51 /* AsyncHomeTimelineViewController+DebugAction.swift in Sources */, DBA0A11325FB3FC10079C110 /* ComposeToolbarView.swift in Sources */, 2D32EADA25CBCC3300C9ED86 /* PublicTimelineViewModel+LoadMiddleState.swift in Sources */, @@ -3738,7 +3834,6 @@ DB45FAF925CA80A2005A8AC7 /* APIService+CoreData+MastodonAuthentication.swift in Sources */, 2D04F42525C255B9003F936F /* APIService+PublicTimeline.swift in Sources */, 2D34D9E226149C920081BFC0 /* SearchRecommendTagsCollectionViewCell.swift in Sources */, - DB1EE7B0267F3088000CC337 /* MastodonStatusContent+Appearance.swift in Sources */, 2D76317D25C14DF500929FB9 /* PublicTimelineViewController+Provider.swift in Sources */, 0F20223326145E51000C64BF /* HashtagTimelineViewModel+LoadMiddleState.swift in Sources */, 2D206B8025F5F45E00143C56 /* UIImage.swift in Sources */, @@ -3747,7 +3842,6 @@ DBE54AC62636C89F004E7C0B /* NotificationPreference.swift in Sources */, 2D5A3D2825CF8BC9002347D6 /* HomeTimelineViewModel+Diffable.swift in Sources */, DB98339C25C96DE600AD9700 /* APIService+Account.swift in Sources */, - 2D42FF6B25C817D2004A627A /* MastodonStatusContent.swift in Sources */, 2DF75BA725D10E1000694EC8 /* APIService+Favorite.swift in Sources */, DB9D6C3825E508BE0051B173 /* Attachment.swift in Sources */, 5DFC35DF262068D20045711D /* SearchViewController+Follow.swift in Sources */, @@ -3766,17 +3860,18 @@ DB938F1F2624382F00E5B6C1 /* ThreadViewModel+Diffable.swift in Sources */, 2D69CFF425CA9E2200C3A1B2 /* LoadMoreConfigurableTableViewContainer.swift in Sources */, DB482A4B261340A7008AE74C /* APIService+UserTimeline.swift in Sources */, - DB3667CA268B14A80027D07F /* ReplicaStatusView.swift in Sources */, DB427DD825BAA00100D1B89D /* SceneDelegate.swift in Sources */, 0F2021FB2613262F000C64BF /* HashtagTimelineViewController.swift in Sources */, DBCC3B30261440A50045B23D /* UITabBarController.swift in Sources */, DB8190C62601FF0400020C08 /* AttachmentContainerView.swift in Sources */, DB51D173262832380062B7A1 /* BlurHashEncode.swift in Sources */, - DB5086AB25CC0BBB00C2C187 /* AvatarConfigurableView.swift in Sources */, 2D32EAAC25CB96DC00C9ED86 /* TimelineMiddleLoaderTableViewCell.swift in Sources */, DB71FD2C25F86A5100512AE1 /* AvatarStackContainerButton.swift in Sources */, DB87D4512609CF1E00D12C0D /* ComposeStatusPollOptionAppendEntryCollectionViewCell.swift in Sources */, + DBBC24C026A5443100398BB9 /* MastodonTheme.swift in Sources */, + DBBC24B526A540AE00398BB9 /* AvatarConfigurableView.swift in Sources */, DB9A489026035963008B817C /* APIService+Media.swift in Sources */, + DBBC24CF26A547AE00398BB9 /* ThemeService+Appearance.swift in Sources */, 2D198649261C0B8500F0B013 /* SearchResultSection.swift in Sources */, DB4F097B26A039FF00D62E92 /* SearchHistorySection.swift in Sources */, DBB525302611EBF3002F1F29 /* ProfilePagingViewModel.swift in Sources */, @@ -3790,18 +3885,14 @@ DB9D6BFF25E4F5940051B173 /* ProfileViewController.swift in Sources */, 0FB3D30825E524C600AAD544 /* PickServerCategoriesCell.swift in Sources */, 2D4AD8A226316CD200613EFC /* SelectedAccountSection.swift in Sources */, - DBD376A72692EA00007FEC24 /* ThemeService.swift in Sources */, - DBD376AE2692EE0A007FEC24 /* MastodonTheme.swift in Sources */, DB789A1225F9F2CC0071ACA0 /* ComposeViewModel.swift in Sources */, DBB525362611ECEB002F1F29 /* UserTimelineViewController.swift in Sources */, DB6D1B3D2636857500ACB481 /* AppearancePreference.swift in Sources */, - DBD376B02692F20F007FEC24 /* SystemTheme.swift in Sources */, DB938F3326243D6200E5B6C1 /* TimelineTopLoaderTableViewCell.swift in Sources */, DB3667A4268AE2370027D07F /* ComposeStatusPollTableViewCell.swift in Sources */, DBBF1DC226524D2900E5B703 /* AutoCompleteTableViewCell.swift in Sources */, 2D38F1FE25CD481700561493 /* StatusProvider.swift in Sources */, DB1EE7B2267F9525000CC337 /* StatusProvider+StatusNodeDelegate.swift in Sources */, - DB03F7F026899097007B274C /* ComposeStatusContentTableViewCell.swift in Sources */, 5B24BBE2262DB19100A9381B /* APIService+Report.swift in Sources */, DB4F096A269EDAD200D62E92 /* SearchResultViewModel+State.swift in Sources */, 5BB04FF5262F0E6D0043BFF6 /* ReportSection.swift in Sources */, @@ -3901,8 +3992,32 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + DBFEF05E26A57715006D7ED1 /* ComposeView.swift in Sources */, + DB41ED7C26A54D5500F58330 /* MastodonStatusContent+Appearance.swift in Sources */, + DBFEF06026A57715006D7ED1 /* StatusAttachmentView.swift in Sources */, DBC6462326A1712000B0E31B /* ShareViewModel.swift in Sources */, + DBBC24B326A53EE700398BB9 /* ActiveLabel.swift in Sources */, + DBBC24CB26A546C000398BB9 /* ThemePreference.swift in Sources */, + DBFEF05F26A57715006D7ED1 /* StatusAuthorView.swift in Sources */, + DBFEF05D26A57715006D7ED1 /* ContentWarningEditorView.swift in Sources */, + DB41ED7B26A54D4D00F58330 /* MastodonStatusContent+ParseResult.swift in Sources */, + DB41ED8A26A54F4C00F58330 /* AttachmentContainerView.swift in Sources */, + DBFEF05C26A57715006D7ED1 /* StatusEditorView.swift in Sources */, + DBBC24C726A5456400398BB9 /* SystemTheme.swift in Sources */, + DBBC24B626A5419700398BB9 /* ComposeStatusContentTableViewCell.swift in Sources */, DBC6462926A1736700B0E31B /* Strings.swift in Sources */, + DBBC24C826A5456400398BB9 /* ThemeService.swift in Sources */, + DBBC24C926A5456400398BB9 /* MastodonTheme.swift in Sources */, + DBBC24B926A5426000398BB9 /* StatusContentWarningEditorView.swift in Sources */, + DB41ED8B26A54F5800F58330 /* AttachmentContainerView+EmptyStateView.swift in Sources */, + DBBC24A826A52F9000398BB9 /* ComposeToolbarView.swift in Sources */, + DB41ED7A26A54D4400F58330 /* MastodonStatusContent.swift in Sources */, + DBFEF05B26A57715006D7ED1 /* ComposeViewModel.swift in Sources */, + DBBC24B026A53DF900398BB9 /* ReplicaStatusView.swift in Sources */, + DBBC24C626A5456000398BB9 /* Theme.swift in Sources */, + DBFEF06326A577F2006D7ED1 /* StatusAttachmentViewModel.swift in Sources */, + DB41ED8926A54F4000F58330 /* ComposeStatusAttachmentCollectionViewCell.swift in Sources */, + DBBC24D226A5488600398BB9 /* AvatarConfigurableView.swift in Sources */, DBC6462C26A176B000B0E31B /* Assets.swift in Sources */, DBC6461526A170AB00B0E31B /* ShareViewController.swift in Sources */, ); @@ -3914,7 +4029,6 @@ files = ( DBE54ACC2636C8FD004E7C0B /* NotificationPreference.swift in Sources */, DB68045B2636DC6A00430867 /* MastodonNotification.swift in Sources */, - DBE54ABF2636C889004E7C0B /* UserDefaults.swift in Sources */, DB6D9F3526351B7A008423CD /* NotificationService+Decrypt.swift in Sources */, DB6804662636DC9000430867 /* String.swift in Sources */, DBCBCBF4267CB070000F5B51 /* Decode85.swift in Sources */, @@ -4169,7 +4283,6 @@ isa = XCBuildConfiguration; baseConfigurationReference = 2E1F6A67FDF9771D3E064FDC /* Pods-Mastodon.debug.xcconfig */; buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CLANG_ENABLE_MODULES = YES; @@ -4197,7 +4310,6 @@ isa = XCBuildConfiguration; baseConfigurationReference = 75E3471C898DDD9631729B6E /* Pods-Mastodon.release.xcconfig */; buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CLANG_ENABLE_MODULES = YES; @@ -4462,7 +4574,9 @@ }; DBC6461D26A170AB00B0E31B /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 77EE917BC055E6621C0452B6 /* Pods-ShareActionExtension.debug.xcconfig */; buildSettings = { + CODE_SIGN_ENTITLEMENTS = ShareActionExtension/ShareActionExtension.entitlements; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 40; DEVELOPMENT_TEAM = 5Z4GVSS33P; @@ -4476,6 +4590,7 @@ PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app.ShareActionExtension; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "APP_EXTENSION $(inherited)"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; @@ -4483,7 +4598,9 @@ }; DBC6461E26A170AB00B0E31B /* ASDK - Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 6130CBE4B26E3C976ACC1688 /* Pods-ShareActionExtension.asdk - debug.xcconfig */; buildSettings = { + CODE_SIGN_ENTITLEMENTS = ShareActionExtension/ShareActionExtension.entitlements; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 40; DEVELOPMENT_TEAM = 5Z4GVSS33P; @@ -4497,6 +4614,7 @@ PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app.ShareActionExtension; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "APP_EXTENSION $(inherited)"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; @@ -4504,7 +4622,9 @@ }; DBC6461F26A170AB00B0E31B /* ASDK - Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 5CE45680252519F42FEA2D13 /* Pods-ShareActionExtension.asdk - release.xcconfig */; buildSettings = { + CODE_SIGN_ENTITLEMENTS = ShareActionExtension/ShareActionExtension.entitlements; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 40; DEVELOPMENT_TEAM = 5Z4GVSS33P; @@ -4518,6 +4638,7 @@ PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app.ShareActionExtension; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "APP_EXTENSION $(inherited)"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; @@ -4525,7 +4646,9 @@ }; DBC6462026A170AB00B0E31B /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 95AD0663479892A2109EEFD0 /* Pods-ShareActionExtension.release.xcconfig */; buildSettings = { + CODE_SIGN_ENTITLEMENTS = ShareActionExtension/ShareActionExtension.entitlements; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 40; DEVELOPMENT_TEAM = 5Z4GVSS33P; @@ -4539,6 +4662,7 @@ PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app.ShareActionExtension; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "APP_EXTENSION $(inherited)"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; @@ -4610,7 +4734,6 @@ isa = XCBuildConfiguration; baseConfigurationReference = BD7598A87F4497045EDEF252 /* Pods-Mastodon.asdk - release.xcconfig */; buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CLANG_ENABLE_MODULES = YES; @@ -4844,7 +4967,6 @@ isa = XCBuildConfiguration; baseConfigurationReference = 1D6D967E77A5357E2C6110D9 /* Pods-Mastodon.asdk - debug.xcconfig */; buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CLANG_ENABLE_MODULES = YES; @@ -5356,6 +5478,26 @@ package = DB3D0FF125BAA61700EAA174 /* XCRemoteSwiftPackageReference "AlamofireImage" */; productName = AlamofireImage; }; + DB41ED7D26A54D6D00F58330 /* Fuzi */ = { + isa = XCSwiftPackageProductDependency; + package = DBAC649F267E6D01007FE9FD /* XCRemoteSwiftPackageReference "Fuzi" */; + productName = Fuzi; + }; + DB41ED7F26A54D7C00F58330 /* AlamofireImage */ = { + isa = XCSwiftPackageProductDependency; + package = DB3D0FF125BAA61700EAA174 /* XCRemoteSwiftPackageReference "AlamofireImage" */; + productName = AlamofireImage; + }; + DB41ED8126A54D8A00F58330 /* MastodonMeta */ = { + isa = XCSwiftPackageProductDependency; + package = DB03F7E9268976B5007B274C /* XCRemoteSwiftPackageReference "MetaTextView" */; + productName = MastodonMeta; + }; + DB41ED8326A54D8A00F58330 /* MetaTextView */ = { + isa = XCSwiftPackageProductDependency; + package = DB03F7E9268976B5007B274C /* XCRemoteSwiftPackageReference "MetaTextView" */; + productName = MetaTextView; + }; DB68050F2637D0F800430867 /* KeychainAccess */ = { isa = XCSwiftPackageProductDependency; package = DB6804722637CC1200430867 /* XCRemoteSwiftPackageReference "KeychainAccess" */; @@ -5401,6 +5543,29 @@ package = DBB525062611EAC0002F1F29 /* XCRemoteSwiftPackageReference "Tabman" */; productName = Tabman; }; + DBBC24A926A5301B00398BB9 /* MastodonSDK */ = { + isa = XCSwiftPackageProductDependency; + productName = MastodonSDK; + }; + DBBC24B126A53ED200398BB9 /* ActiveLabel */ = { + isa = XCSwiftPackageProductDependency; + package = 2D42FF5F25C8177C004A627A /* XCRemoteSwiftPackageReference "ActiveLabel" */; + productName = ActiveLabel; + }; + DBBC24B726A5421800398BB9 /* CommonOSLog */ = { + isa = XCSwiftPackageProductDependency; + package = DB0140BB25C40D7500F9F3CF /* XCRemoteSwiftPackageReference "CommonOSLog" */; + productName = CommonOSLog; + }; + DBBC24CC26A5471E00398BB9 /* MastodonExtension */ = { + isa = XCSwiftPackageProductDependency; + productName = MastodonExtension; + }; + DBBC24D026A5484F00398BB9 /* UITextView+Placeholder */ = { + isa = XCSwiftPackageProductDependency; + package = DB9A487C2603456B008B817C /* XCRemoteSwiftPackageReference "UITextView-Placeholder" */; + productName = "UITextView+Placeholder"; + }; DBC6462426A1720B00B0E31B /* MastodonUI */ = { isa = XCSwiftPackageProductDependency; productName = MastodonUI; diff --git a/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist b/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist index e417a0d4..4dc2e933 100644 --- a/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist @@ -12,27 +12,27 @@ CoreDataStack.xcscheme_^#shared#^_ orderHint - 20 + 23 Mastodon - ASDK.xcscheme_^#shared#^_ orderHint - 5 + 2 Mastodon - RTL.xcscheme_^#shared#^_ orderHint - 7 + 3 Mastodon - Release.xcscheme_^#shared#^_ orderHint - 3 + 1 Mastodon.xcscheme_^#shared#^_ orderHint - 1 + 0 NotificationService.xcscheme_^#shared#^_ diff --git a/Mastodon.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Mastodon.xcworkspace/xcshareddata/swiftpm/Package.resolved index 78b4a755..55f7d711 100644 --- a/Mastodon.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Mastodon.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -163,6 +163,15 @@ "version": "1.0.0" } }, + { + "package": "Introspect", + "repositoryURL": "https://github.com/siteline/SwiftUI-Introspect.git", + "state": { + "branch": null, + "revision": "2e09be8af614401bc9f87d40093ec19ce56ccaf2", + "version": "0.1.3" + } + }, { "package": "SwiftyJSON", "repositoryURL": "https://github.com/SwiftyJSON/SwiftyJSON.git", diff --git a/Mastodon/Extension/ActiveLabel.swift b/Mastodon/Extension/ActiveLabel.swift index ebb82655..58488819 100644 --- a/Mastodon/Extension/ActiveLabel.swift +++ b/Mastodon/Extension/ActiveLabel.swift @@ -9,6 +9,7 @@ import UIKit import Foundation import ActiveLabel import os.log +import MastodonUI extension ActiveLabel { @@ -58,7 +59,7 @@ extension ActiveLabel { } extension ActiveLabel { - func configure(text: String) { + public func configure(text: String) { attributedText = nil activeEntities.removeAll() self.text = text @@ -69,7 +70,7 @@ extension ActiveLabel { extension ActiveLabel { /// status content - func configure(content: String, emojiDict: MastodonStatusContent.EmojiDict) { + public func configure(content: String, emojiDict: MastodonStatusContent.EmojiDict) { attributedText = nil activeEntities.removeAll() @@ -83,7 +84,7 @@ extension ActiveLabel { } } - func configure(contentParseResult parseResult: MastodonStatusContent.ParseResult?) { + public func configure(contentParseResult parseResult: MastodonStatusContent.ParseResult?) { attributedText = nil activeEntities.removeAll() text = parseResult?.trimmed ?? "" @@ -92,14 +93,14 @@ extension ActiveLabel { } /// account note - func configure(note: String, emojiDict: MastodonStatusContent.EmojiDict) { + public func configure(note: String, emojiDict: MastodonStatusContent.EmojiDict) { configure(content: note, emojiDict: emojiDict) } } extension ActiveLabel { /// account field - func configure(field: String, emojiDict: MastodonStatusContent.EmojiDict) { + public func configure(field: String, emojiDict: MastodonStatusContent.EmojiDict) { configure(content: field, emojiDict: emojiDict) } } diff --git a/Mastodon/Helper/MastodonField.swift b/Mastodon/Helper/MastodonField.swift index 437b0924..86bc2cc9 100644 --- a/Mastodon/Helper/MastodonField.swift +++ b/Mastodon/Helper/MastodonField.swift @@ -5,59 +5,59 @@ // Created by MainasuK Cirno on 2021-3-30. // -import Foundation -import ActiveLabel - -enum MastodonField { - - @available(*, deprecated, message: "rely on server meta rendering") - static func parse(field string: String, emojiDict: MastodonStatusContent.EmojiDict) -> ParseResult { - // use content parser get emoji entities - let value = string - - var string = string - var entities: [ActiveEntity] = [] - - do { - let contentParseresult = try MastodonStatusContent.parse(content: string, emojiDict: emojiDict) - string = contentParseresult.trimmed - entities.append(contentsOf: contentParseresult.activeEntities) - } catch { - // assertionFailure(error.localizedDescription) - } - - let mentionMatches = string.matches(pattern: "(?:@([a-zA-Z0-9_]+)(@[a-zA-Z0-9_.-]+)?)") - let hashtagMatches = string.matches(pattern: "(?:#([^\\s.]+))") - let urlMatches = string.matches(pattern: "(?i)https?://\\S+(?:/|\\b)") - - - for match in mentionMatches { - guard let text = string.substring(with: match, at: 0) else { continue } - let entity = ActiveEntity(range: match.range, type: .mention(text, userInfo: nil)) - entities.append(entity) - } - - for match in hashtagMatches { - guard let text = string.substring(with: match, at: 0) else { continue } - let entity = ActiveEntity(range: match.range, type: .hashtag(text, userInfo: nil)) - entities.append(entity) - } - - for match in urlMatches { - guard let text = string.substring(with: match, at: 0) else { continue } - let entity = ActiveEntity(range: match.range, type: .url(text, trimmed: text, url: text, userInfo: nil)) - entities.append(entity) - } - - return ParseResult(value: value, trimmed: string, activeEntities: entities) - } - -} - -extension MastodonField { - struct ParseResult { - let value: String - let trimmed: String - let activeEntities: [ActiveEntity] - } -} +//import Foundation +//import ActiveLabel +// +//enum MastodonField { +// +// @available(*, deprecated, message: "rely on server meta rendering") +// public static func parse(field string: String, emojiDict: MastodonStatusContent.EmojiDict) -> ParseResult { +// // use content parser get emoji entities +// let value = string +// +// var string = string +// var entities: [ActiveEntity] = [] +// +// do { +// let contentParseresult = try MastodonStatusContent.parse(content: string, emojiDict: emojiDict) +// string = contentParseresult.trimmed +// entities.append(contentsOf: contentParseresult.activeEntities) +// } catch { +// // assertionFailure(error.localizedDescription) +// } +// +// let mentionMatches = string.matches(pattern: "(?:@([a-zA-Z0-9_]+)(@[a-zA-Z0-9_.-]+)?)") +// let hashtagMatches = string.matches(pattern: "(?:#([^\\s.]+))") +// let urlMatches = string.matches(pattern: "(?i)https?://\\S+(?:/|\\b)") +// +// +// for match in mentionMatches { +// guard let text = string.substring(with: match, at: 0) else { continue } +// let entity = ActiveEntity(range: match.range, type: .mention(text, userInfo: nil)) +// entities.append(entity) +// } +// +// for match in hashtagMatches { +// guard let text = string.substring(with: match, at: 0) else { continue } +// let entity = ActiveEntity(range: match.range, type: .hashtag(text, userInfo: nil)) +// entities.append(entity) +// } +// +// for match in urlMatches { +// guard let text = string.substring(with: match, at: 0) else { continue } +// let entity = ActiveEntity(range: match.range, type: .url(text, trimmed: text, url: text, userInfo: nil)) +// entities.append(entity) +// } +// +// return ParseResult(value: value, trimmed: string, activeEntities: entities) +// } +// +//} +// +//extension MastodonField { +// public struct ParseResult { +// let value: String +// let trimmed: String +// let activeEntities: [ActiveEntity] +// } +//} diff --git a/Mastodon/Helper/MastodonMetricFormatter.swift b/Mastodon/Helper/MastodonMetricFormatter.swift index 0711669f..3c9c4dd7 100644 --- a/Mastodon/Helper/MastodonMetricFormatter.swift +++ b/Mastodon/Helper/MastodonMetricFormatter.swift @@ -7,9 +7,9 @@ import Foundation -final class MastodonMetricFormatter: Formatter { +final public class MastodonMetricFormatter: Formatter { - func string(from number: Int) -> String? { + public func string(from number: Int) -> String? { let isPositive = number >= 0 let symbol = isPositive ? "" : "-" diff --git a/Mastodon/Helper/MastodonRegex.swift b/Mastodon/Helper/MastodonRegex.swift index c390ea51..c8c3f498 100644 --- a/Mastodon/Helper/MastodonRegex.swift +++ b/Mastodon/Helper/MastodonRegex.swift @@ -7,19 +7,19 @@ import Foundation -enum MastodonRegex { +public enum MastodonRegex { /// mention, hashtag. /// @... /// #... - static let highlightPattern = "(?:@([a-zA-Z0-9_]+)(@[a-zA-Z0-9_.-]+)?|#([^\\s.]+))" + public static let highlightPattern = "(?:@([a-zA-Z0-9_]+)(@[a-zA-Z0-9_.-]+)?|#([^\\s.]+))" /// emoji /// :shortcode: /// accept ^\B: or \s: but not accept \B: to force user input a space to make emoji take effect /// precondition :\B with following space - static let emojiPattern = "(?:(^\\B:|\\s:)([a-zA-Z0-9_]+)(:\\B(?=\\s)))" + public static let emojiPattern = "(?:(^\\B:|\\s:)([a-zA-Z0-9_]+)(:\\B(?=\\s)))" /// mention, hashtag, emoji /// @… /// #… /// :… - static let autoCompletePattern = "(?:@([a-zA-Z0-9_]+)(@[a-zA-Z0-9_.-]+)?|#([^\\s.]+))|(^\\B:|\\s:)([a-zA-Z0-9_]+)" + public static let autoCompletePattern = "(?:@([a-zA-Z0-9_]+)(@[a-zA-Z0-9_.-]+)?|#([^\\s.]+))|(^\\B:|\\s:)([a-zA-Z0-9_]+)" } diff --git a/Mastodon/Helper/MastodonStatusContent+Appearance.swift b/Mastodon/Helper/MastodonStatusContent+Appearance.swift index f627093c..d82d68b8 100644 --- a/Mastodon/Helper/MastodonStatusContent+Appearance.swift +++ b/Mastodon/Helper/MastodonStatusContent+Appearance.swift @@ -8,7 +8,7 @@ import UIKit extension MastodonStatusContent { - struct Appearance { + public struct Appearance { let attributes: [NSAttributedString.Key: Any] let urlAttributes: [NSAttributedString.Key: Any] let hashtagAttributes: [NSAttributedString.Key: Any] diff --git a/Mastodon/Helper/MastodonStatusContent+ParseResult.swift b/Mastodon/Helper/MastodonStatusContent+ParseResult.swift index f1f02fae..9de0b341 100644 --- a/Mastodon/Helper/MastodonStatusContent+ParseResult.swift +++ b/Mastodon/Helper/MastodonStatusContent+ParseResult.swift @@ -9,20 +9,20 @@ import Foundation import ActiveLabel extension MastodonStatusContent { - struct ParseResult: Hashable { - let document: String - let original: String - let trimmed: String - let activeEntities: [ActiveEntity] + public struct ParseResult: Hashable { + public let document: String + public let original: String + public let trimmed: String + public let activeEntities: [ActiveEntity] - static func == (lhs: MastodonStatusContent.ParseResult, rhs: MastodonStatusContent.ParseResult) -> Bool { + public static func == (lhs: MastodonStatusContent.ParseResult, rhs: MastodonStatusContent.ParseResult) -> Bool { return lhs.document == rhs.document && lhs.original == rhs.original && lhs.trimmed == rhs.trimmed && lhs.activeEntities.count == rhs.activeEntities.count // FIXME: } - func hash(into hasher: inout Hasher) { + public func hash(into hasher: inout Hasher) { hasher.combine(document) hasher.combine(original) hasher.combine(trimmed) @@ -57,7 +57,7 @@ extension ActiveEntityType { static let appScheme = "mastodon" - init?(url: URL) { + public init?(url: URL) { guard let scheme = url.scheme?.lowercased() else { return nil } guard scheme == ActiveEntityType.appScheme else { self = .url("", trimmed: "", url: url.absoluteString, userInfo: nil) @@ -78,7 +78,7 @@ extension ActiveEntityType { return nil } - var uri: URL? { + public var uri: URL? { switch self { case .url(_, _, let url, _): return URL(string: url) diff --git a/Mastodon/Helper/MastodonStatusContent.swift b/Mastodon/Helper/MastodonStatusContent.swift index d19463a8..90e697da 100755 --- a/Mastodon/Helper/MastodonStatusContent.swift +++ b/Mastodon/Helper/MastodonStatusContent.swift @@ -10,14 +10,14 @@ import Combine import ActiveLabel import Fuzi -enum MastodonStatusContent { +public enum MastodonStatusContent { - typealias EmojiShortcode = String - typealias EmojiDict = [EmojiShortcode: URL] + public typealias EmojiShortcode = String + public typealias EmojiDict = [EmojiShortcode: URL] static let workingQueue = DispatchQueue(label: "org.joinmastodon.app.ActiveLabel.working-queue", qos: .userInteractive, attributes: .concurrent) - static func parseResult(content: String, emojiDict: MastodonStatusContent.EmojiDict) -> AnyPublisher { + public static func parseResult(content: String, emojiDict: MastodonStatusContent.EmojiDict) -> AnyPublisher { return Future { promise in self.workingQueue.async { let parseResult = try? MastodonStatusContent.parse(content: content, emojiDict: emojiDict) @@ -27,7 +27,7 @@ enum MastodonStatusContent { .eraseToAnyPublisher() } - static func parse(content: String, emojiDict: EmojiDict) throws -> MastodonStatusContent.ParseResult { + public static func parse(content: String, emojiDict: EmojiDict) throws -> MastodonStatusContent.ParseResult { let document: String = { var content = content for (shortcode, url) in emojiDict { diff --git a/Mastodon/Preference/AppearancePreference.swift b/Mastodon/Preference/AppearancePreference.swift index 1b4c4280..4630c123 100644 --- a/Mastodon/Preference/AppearancePreference.swift +++ b/Mastodon/Preference/AppearancePreference.swift @@ -19,7 +19,8 @@ extension UserDefaults { @objc dynamic var preferredStaticAvatar: Bool { get { - register(defaults: [#function: false]) + // default false + // without set register to profile timeline performance return bool(forKey: #function) } set { self[#function] = newValue } diff --git a/Mastodon/Preference/NotificationPreference.swift b/Mastodon/Preference/NotificationPreference.swift index 289cd1fd..63092d56 100644 --- a/Mastodon/Preference/NotificationPreference.swift +++ b/Mastodon/Preference/NotificationPreference.swift @@ -6,6 +6,7 @@ // import UIKit +import MastodonExtension extension UserDefaults { diff --git a/Mastodon/Preference/SplashPreference.swift b/Mastodon/Preference/SplashPreference.swift deleted file mode 100644 index c622b865..00000000 --- a/Mastodon/Preference/SplashPreference.swift +++ /dev/null @@ -1,12 +0,0 @@ -// -// SplashPreference.swift -// Mastodon -// -// Created by Cirno MainasuK on 2020-2-4. -// - -import UIKit - -extension UserDefaults { - // TODO: splash scene -} diff --git a/Mastodon/Preference/ThemePreference.swift b/Mastodon/Preference/ThemePreference.swift index 5faf6097..62404779 100644 --- a/Mastodon/Preference/ThemePreference.swift +++ b/Mastodon/Preference/ThemePreference.swift @@ -6,6 +6,7 @@ // import UIKit +import MastodonExtension extension UserDefaults { diff --git a/Mastodon/Protocol/AvatarConfigurableView.swift b/Mastodon/Protocol/AvatarConfigurableView.swift index 9fe56ab4..5807eed1 100644 --- a/Mastodon/Protocol/AvatarConfigurableView.swift +++ b/Mastodon/Protocol/AvatarConfigurableView.swift @@ -24,11 +24,22 @@ extension AvatarConfigurableView { public func configure(with configuration: AvatarConfigurableViewConfiguration) { let placeholderImage: UIImage = { guard let placeholderImage = configuration.placeholderImage else { + #if APP_EXTENSION + let placeholderImage = configuration.placeholderImage ?? UIImage.placeholder(size: Self.configurableAvatarImageSize, color: .systemFill) + if Self.configurableAvatarImageCornerRadius < Self.configurableAvatarImageSize.width * 0.5 { + return placeholderImage + .af.imageAspectScaled(toFill: Self.configurableAvatarImageSize) + .af.imageRounded(withCornerRadius: Self.configurableAvatarImageCornerRadius, divideRadiusByImageScale: false) + } else { + return placeholderImage.af.imageRoundedIntoCircle() + } + #else return AppContext.shared.placeholderImageCacheService.image( color: .systemFill, size: Self.configurableAvatarImageSize, cornerRadius: Self.configurableAvatarImageCornerRadius ) + #endif } return placeholderImage }() @@ -115,7 +126,7 @@ extension AvatarConfigurableView { } struct AvatarConfigurableViewConfiguration { - + let avatarImageURL: URL? let placeholderImage: UIImage? let borderColor: UIColor? diff --git a/Mastodon/Scene/Compose/CollectionViewCell/ComposeStatusAttachmentCollectionViewCell.swift b/Mastodon/Scene/Compose/CollectionViewCell/ComposeStatusAttachmentCollectionViewCell.swift index a5906a9d..8d8cabcf 100644 --- a/Mastodon/Scene/Compose/CollectionViewCell/ComposeStatusAttachmentCollectionViewCell.swift +++ b/Mastodon/Scene/Compose/CollectionViewCell/ComposeStatusAttachmentCollectionViewCell.swift @@ -8,13 +8,16 @@ import os.log import UIKit import Combine +import MastodonUI protocol ComposeStatusAttachmentCollectionViewCellDelegate: AnyObject { func composeStatusAttachmentCollectionViewCell(_ cell: ComposeStatusAttachmentCollectionViewCell, removeButtonDidPressed button: UIButton) } final class ComposeStatusAttachmentCollectionViewCell: UICollectionViewCell { - + + let logger = Logger(subsystem: "ComposeStatusAttachmentCollectionViewCell", category: "UI") + var disposeBag = Set() static let verticalMarginHeight: CGFloat = ComposeStatusAttachmentCollectionViewCell.removeButtonSize.height * 0.5 @@ -58,7 +61,7 @@ final class ComposeStatusAttachmentCollectionViewCell: UICollectionViewCell { } deinit { - os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function) + logger.debug("\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public)") } } @@ -96,7 +99,7 @@ extension ComposeStatusAttachmentCollectionViewCell { extension ComposeStatusAttachmentCollectionViewCell { @objc private func removeButtonDidPressed(_ sender: UIButton) { - os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function) + logger.debug("\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public)") delegate?.composeStatusAttachmentCollectionViewCell(self, removeButtonDidPressed: sender) } diff --git a/Mastodon/Scene/Compose/TableViewCell/ComposeStatusContentTableViewCell.swift b/Mastodon/Scene/Compose/CollectionViewCell/ComposeStatusContentTableViewCell.swift similarity index 95% rename from Mastodon/Scene/Compose/TableViewCell/ComposeStatusContentTableViewCell.swift rename to Mastodon/Scene/Compose/CollectionViewCell/ComposeStatusContentTableViewCell.swift index aa641fe4..7dbda5b3 100644 --- a/Mastodon/Scene/Compose/TableViewCell/ComposeStatusContentTableViewCell.swift +++ b/Mastodon/Scene/Compose/CollectionViewCell/ComposeStatusContentTableViewCell.swift @@ -5,14 +5,16 @@ // Created by MainasuK Cirno on 2021-6-28. // - import os.log import UIKit import Combine import MetaTextView +import UITextView_Placeholder final class ComposeStatusContentTableViewCell: UITableViewCell { + let logger = Logger(subsystem: "ComposeStatusContentTableViewCell", category: "UI") + var disposeBag = Set() let statusView = ReplicaStatusView() @@ -149,7 +151,7 @@ extension ComposeStatusContentTableViewCell: UITextViewDelegate { } func textViewDidChange(_ textView: UITextView) { - os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: text: %s", ((#file as NSString).lastPathComponent), #line, #function, textView.text) + logger.debug("\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): text: \(textView.text ?? "")") guard textView === statusContentWarningEditorView.textView else { return } // replace line break with space textView.text = textView.text.replacingOccurrences(of: "\n", with: " ") diff --git a/Mastodon/Scene/Compose/ComposeViewController.swift b/Mastodon/Scene/Compose/ComposeViewController.swift index b48b01a7..b48bcc63 100644 --- a/Mastodon/Scene/Compose/ComposeViewController.swift +++ b/Mastodon/Scene/Compose/ComposeViewController.swift @@ -76,7 +76,11 @@ final class ComposeViewController: UIViewController, NeedsDependency { let composeToolbarView = ComposeToolbarView() var composeToolbarViewBottomLayoutConstraint: NSLayoutConstraint! - let composeToolbarBackgroundView = UIView() + let composeToolbarBackgroundView: UIView = { + let view = UIView() + view.backgroundColor = Asset.Scene.Compose.toolbarBackground.color + return view + }() static func createPhotoLibraryPickerConfiguration(selectionLimit: Int = 4) -> PHPickerConfiguration { var configuration = PHPickerConfiguration() @@ -189,7 +193,7 @@ extension ComposeViewController { ]) tableView.delegate = self - viewModel.setupDiffableDataSource( + viewModel.setupDataSource( tableView: tableView, metaTextDelegate: self, metaTextViewDelegate: self, @@ -264,7 +268,6 @@ extension ComposeViewController { self.view.layoutIfNeeded() } } - self.updateKeyboardBackground(isKeyboardDisplay: isShow) return } // isShow AND dock state @@ -280,14 +283,12 @@ extension ComposeViewController { self.autoCompleteViewController.tableView.contentInset.bottom = autoCompleteTableViewBottomInset self.autoCompleteViewController.tableView.verticalScrollIndicatorInsets.bottom = autoCompleteTableViewBottomInset - // adjust inset for collectionView + // adjust inset for tableView let contentFrame = self.view.convert(self.tableView.frame, to: nil) let padding = contentFrame.maxY + extraMargin - endFrame.minY guard padding > 0 else { self.tableView.contentInset.bottom = self.view.safeAreaInsets.bottom + extraMargin self.tableView.verticalScrollIndicatorInsets.bottom = self.view.safeAreaInsets.bottom + extraMargin - - self.updateKeyboardBackground(isKeyboardDisplay: false) return } @@ -297,7 +298,6 @@ extension ComposeViewController { self.composeToolbarViewBottomLayoutConstraint.constant = endFrame.height self.view.layoutIfNeeded() } - self.updateKeyboardBackground(isKeyboardDisplay: isShow) }) .store(in: &disposeBag) @@ -587,10 +587,6 @@ extension ComposeViewController { imagePicker.delegate = self return imagePicker } - - private func updateKeyboardBackground(isKeyboardDisplay: Bool) { - composeToolbarBackgroundView.backgroundColor = Asset.Scene.Compose.toolbarBackground.color - } private func setupBackgroundColor(theme: Theme) { view.backgroundColor = theme.systemElevatedBackgroundColor diff --git a/Mastodon/Scene/Compose/ComposeViewModel+Diffable.swift b/Mastodon/Scene/Compose/ComposeViewModel+DataSource.swift similarity index 99% rename from Mastodon/Scene/Compose/ComposeViewModel+Diffable.swift rename to Mastodon/Scene/Compose/ComposeViewModel+DataSource.swift index 59de0d60..4b4e80c5 100644 --- a/Mastodon/Scene/Compose/ComposeViewModel+Diffable.swift +++ b/Mastodon/Scene/Compose/ComposeViewModel+DataSource.swift @@ -16,7 +16,7 @@ import MetaTextView extension ComposeViewModel { - func setupDiffableDataSource( + func setupDataSource( tableView: UITableView, metaTextDelegate: MetaTextDelegate, metaTextViewDelegate: UITextViewDelegate, diff --git a/Mastodon/Scene/Compose/View/AttachmentContainerView+EmptyStateView.swift b/Mastodon/Scene/Compose/View/AttachmentContainerView+EmptyStateView.swift index b9e92f51..b441fa25 100644 --- a/Mastodon/Scene/Compose/View/AttachmentContainerView+EmptyStateView.swift +++ b/Mastodon/Scene/Compose/View/AttachmentContainerView+EmptyStateView.swift @@ -6,6 +6,7 @@ // import UIKit +import MastodonUI extension AttachmentContainerView { final class EmptyStateView: UIView { diff --git a/Mastodon/Scene/Compose/View/ComposeToolbarView.swift b/Mastodon/Scene/Compose/View/ComposeToolbarView.swift index 3edb17e4..aecd5d06 100644 --- a/Mastodon/Scene/Compose/View/ComposeToolbarView.swift +++ b/Mastodon/Scene/Compose/View/ComposeToolbarView.swift @@ -96,9 +96,6 @@ final class ComposeToolbarView: UIView { extension ComposeToolbarView { private func _init() { - // magic keyboard color (iOS 14): - // light with white background: RGB 214 216 222 - // dark with black background: RGB 43 43 43 backgroundColor = Asset.Scene.Compose.toolbarBackground.color let stackView = UIStackView() diff --git a/Mastodon/Scene/Compose/View/ReplicaStatusView.swift b/Mastodon/Scene/Compose/View/ReplicaStatusView.swift index 0f53b113..9dde2f63 100644 --- a/Mastodon/Scene/Compose/View/ReplicaStatusView.swift +++ b/Mastodon/Scene/Compose/View/ReplicaStatusView.swift @@ -48,7 +48,7 @@ final class ReplicaStatusView: UIView { let headerIconLabel: UILabel = { let label = UILabel() - label.attributedText = StatusView.iconAttributedString(image: StatusView.reblogIconImage) + label.attributedText = ReplicaStatusView.iconAttributedString(image: ReplicaStatusView.reblogIconImage) return label }() @@ -67,7 +67,6 @@ final class ReplicaStatusView: UIView { return view }() let avatarImageView: UIImageView = FLAnimatedImageView() - let avatarStackedContainerButton: AvatarStackContainerButton = AvatarStackContainerButton() let nameLabel: ActiveLabel = { let label = ActiveLabel(style: .statusName) @@ -157,7 +156,7 @@ extension ReplicaStatusView { headerContainerStackView.topAnchor.constraint(equalTo: headerContainerView.topAnchor), headerContainerStackView.leadingAnchor.constraint(equalTo: headerContainerView.leadingAnchor), headerContainerStackView.trailingAnchor.constraint(equalTo: headerContainerView.trailingAnchor), - headerContainerView.bottomAnchor.constraint(equalTo: headerContainerStackView.bottomAnchor, constant: StatusView.containerStackViewSpacing).priority(.defaultHigh), + headerContainerView.bottomAnchor.constraint(equalTo: headerContainerStackView.bottomAnchor, constant: ReplicaStatusView.containerStackViewSpacing).priority(.defaultHigh), ]) containerStackView.addArrangedSubview(headerContainerView) defer { @@ -167,15 +166,15 @@ extension ReplicaStatusView { // author container: [avatar | author meta container | reveal button] let authorContainerStackView = UIStackView() authorContainerStackView.axis = .horizontal - authorContainerStackView.spacing = StatusView.avatarToLabelSpacing + authorContainerStackView.spacing = ReplicaStatusView.avatarToLabelSpacing authorContainerStackView.distribution = .fill // avatar avatarView.translatesAutoresizingMaskIntoConstraints = false authorContainerStackView.addArrangedSubview(avatarView) NSLayoutConstraint.activate([ - avatarView.widthAnchor.constraint(equalToConstant: StatusView.avatarImageSize.width).priority(.required - 1), - avatarView.heightAnchor.constraint(equalToConstant: StatusView.avatarImageSize.height).priority(.required - 1), + avatarView.widthAnchor.constraint(equalToConstant: ReplicaStatusView.avatarImageSize.width).priority(.required - 1), + avatarView.heightAnchor.constraint(equalToConstant: ReplicaStatusView.avatarImageSize.height).priority(.required - 1), ]) avatarImageView.translatesAutoresizingMaskIntoConstraints = false avatarView.addSubview(avatarImageView) @@ -185,14 +184,6 @@ extension ReplicaStatusView { avatarImageView.trailingAnchor.constraint(equalTo: avatarView.trailingAnchor), avatarImageView.bottomAnchor.constraint(equalTo: avatarView.bottomAnchor), ]) - avatarStackedContainerButton.translatesAutoresizingMaskIntoConstraints = false - avatarView.addSubview(avatarStackedContainerButton) - NSLayoutConstraint.activate([ - avatarStackedContainerButton.topAnchor.constraint(equalTo: avatarView.topAnchor), - avatarStackedContainerButton.leadingAnchor.constraint(equalTo: avatarView.leadingAnchor), - avatarStackedContainerButton.trailingAnchor.constraint(equalTo: avatarView.trailingAnchor), - avatarStackedContainerButton.bottomAnchor.constraint(equalTo: avatarView.bottomAnchor), - ]) // author meta container: [title container | subtitle container] let authorMetaContainerStackView = UIStackView() @@ -235,7 +226,7 @@ extension ReplicaStatusView { authorContainerStackView.topAnchor.constraint(equalTo: authorContainerView.topAnchor), authorContainerStackView.leadingAnchor.constraint(equalTo: authorContainerView.leadingAnchor), authorContainerStackView.trailingAnchor.constraint(equalTo: authorContainerView.trailingAnchor), - authorContainerView.bottomAnchor.constraint(equalTo: authorContainerStackView.bottomAnchor, constant: StatusView.containerStackViewSpacing).priority(.defaultHigh), + authorContainerView.bottomAnchor.constraint(equalTo: authorContainerStackView.bottomAnchor, constant: ReplicaStatusView.containerStackViewSpacing).priority(.defaultHigh), ]) containerStackView.addArrangedSubview(authorContainerView) @@ -252,8 +243,6 @@ extension ReplicaStatusView { // status statusContainerStackView.addArrangedSubview(contentMetaText.textView) contentMetaText.textView.setContentCompressionResistancePriority(.required - 1, for: .vertical) - - avatarStackedContainerButton.isHidden = true } } diff --git a/Mastodon/Scene/Compose/View/StatusContentWarningEditorView.swift b/Mastodon/Scene/Compose/View/StatusContentWarningEditorView.swift index 1d7ae65e..1ce274a5 100644 --- a/Mastodon/Scene/Compose/View/StatusContentWarningEditorView.swift +++ b/Mastodon/Scene/Compose/View/StatusContentWarningEditorView.swift @@ -6,6 +6,7 @@ // import UIKit +import MastodonUI final class StatusContentWarningEditorView: UIView { @@ -72,28 +73,6 @@ extension StatusContentWarningEditorView { containerStackView.addArrangedSubview(iconImageView) iconImageView.setContentHuggingPriority(.required - 1, for: .horizontal) containerStackView.addArrangedSubview(textView) - -// iconImageView.translatesAutoresizingMaskIntoConstraints = false -// addSubview(iconImageView) -// NSLayoutConstraint.activate([ -// iconImageView.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor), -// iconImageView.widthAnchor.constraint(equalToConstant: StatusView.avatarImageSize.width).priority(.defaultHigh), // center alignment to avatar -// ]) -// iconImageView.setContentHuggingPriority(.required - 2, for: .horizontal) -// -// textView.translatesAutoresizingMaskIntoConstraints = false -// addSubview(textView) -// NSLayoutConstraint.activate([ -// textView.centerYAnchor.constraint(equalTo: centerYAnchor), -// textView.topAnchor.constraint(equalTo: topAnchor, constant: 6), -// textView.leadingAnchor.constraint(equalTo: iconImageView.trailingAnchor, constant: StatusView.avatarToLabelSpacing - 4), // align to name label. minus magic 4pt to remove addition inset -// textView.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor), -// bottomAnchor.constraint(equalTo: textView.bottomAnchor, constant: 6), -// textView.heightAnchor.constraint(greaterThanOrEqualToConstant: 44).priority(.defaultHigh), -// ]) -// -// textView.setContentHuggingPriority(.required - 1, for: .vertical) -// textView.setContentCompressionResistancePriority(.required - 1, for: .vertical) } } diff --git a/Mastodon/Scene/HomeTimeline/HomeTimelineViewController+DebugAction.swift b/Mastodon/Scene/HomeTimeline/HomeTimelineViewController+DebugAction.swift index 0b439753..a55f1ebf 100644 --- a/Mastodon/Scene/HomeTimeline/HomeTimelineViewController+DebugAction.swift +++ b/Mastodon/Scene/HomeTimeline/HomeTimelineViewController+DebugAction.swift @@ -57,10 +57,6 @@ extension HomeTimelineViewController { guard let self = self else { return } self.showThreadAction(action) }, - UIAction(title: "Show Share Action Compose", image: UIImage(systemName: "square.and.arrow.up"), attributes: []) { [weak self] action in - guard let self = self else { return } - self.showShareActionExtensionComposeView(action) - }, UIAction(title: "Settings", image: UIImage(systemName: "gear"), attributes: []) { [weak self] action in guard let self = self else { return } self.showSettings(action) @@ -370,13 +366,5 @@ extension HomeTimelineViewController { ) } - @objc private func showShareActionExtensionComposeView(_ sender: UIAction) { - let viewController = UIHostingController( - rootView: ComposeView().environmentObject(MastodonUI.ComposeViewModel()) - ) - let navigationController = UINavigationController(rootViewController: viewController) - present(navigationController, animated: true, completion: nil) - } - } #endif diff --git a/Mastodon/Scene/Profile/Header/ProfileHeaderViewController.swift b/Mastodon/Scene/Profile/Header/ProfileHeaderViewController.swift index 618faf1c..a88d3e06 100644 --- a/Mastodon/Scene/Profile/Header/ProfileHeaderViewController.swift +++ b/Mastodon/Scene/Profile/Header/ProfileHeaderViewController.swift @@ -465,7 +465,7 @@ extension ProfileHeaderViewController: PHPickerViewControllerDelegate { func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) { picker.dismiss(animated: true, completion: nil) guard let result = results.first else { return } - PHPickerResultLoader.loadImageData(from: result) + ItemProviderLoader.loadImageData(from: result) .sink { [weak self] completion in guard let _ = self else { return } switch completion { diff --git a/Mastodon/Service/MastodonAttachmentService/MastodonAttachmentService.swift b/Mastodon/Service/MastodonAttachmentService/MastodonAttachmentService.swift index cafa3145..f2841b26 100644 --- a/Mastodon/Service/MastodonAttachmentService/MastodonAttachmentService.swift +++ b/Mastodon/Service/MastodonAttachmentService/MastodonAttachmentService.swift @@ -12,6 +12,7 @@ import PhotosUI import GameplayKit import MobileCoreServices import MastodonSDK +import MastodonUI protocol MastodonAttachmentServiceDelegate: AnyObject { func mastodonAttachmentService(_ service: MastodonAttachmentService, uploadStateDidChange state: MastodonAttachmentService.UploadState?) @@ -62,10 +63,10 @@ final class MastodonAttachmentService { Just(pickerResult) .flatMap { result -> AnyPublisher in if result.itemProvider.hasRepresentationConforming(toTypeIdentifier: UTType.image.identifier, fileOptions: []) { - return PHPickerResultLoader.loadImageData(from: result).eraseToAnyPublisher() + return ItemProviderLoader.loadImageData(from: result).eraseToAnyPublisher() } if result.itemProvider.hasRepresentationConforming(toTypeIdentifier: UTType.movie.identifier, fileOptions: []) { - return PHPickerResultLoader.loadVideoData(from: result).eraseToAnyPublisher() + return ItemProviderLoader.loadVideoData(from: result).eraseToAnyPublisher() } return Fail(error: AttachmentError.invalidAttachmentType).eraseToAnyPublisher() } @@ -186,7 +187,6 @@ extension MastodonAttachmentService { case invalidAttachmentType case attachmentTooLarge } - } extension MastodonAttachmentService { diff --git a/Mastodon/Service/ThemeService/Theme.swift b/Mastodon/Service/ThemeService/Theme.swift index 6516945d..c9378e87 100644 --- a/Mastodon/Service/ThemeService/Theme.swift +++ b/Mastodon/Service/ThemeService/Theme.swift @@ -7,7 +7,7 @@ import UIKit -protocol Theme { +public protocol Theme { var systemBackgroundColor: UIColor { get } var secondarySystemBackgroundColor: UIColor { get } var tertiarySystemBackgroundColor: UIColor { get } @@ -36,13 +36,13 @@ protocol Theme { } -enum ThemeName: String, CaseIterable { +public enum ThemeName: String, CaseIterable { case system case mastodon } extension ThemeName { - var theme: Theme { + public var theme: Theme { switch self { case .system: return SystemTheme() case .mastodon: return MastodonTheme() diff --git a/Mastodon/Service/ThemeService/ThemeService+Appearance.swift b/Mastodon/Service/ThemeService/ThemeService+Appearance.swift new file mode 100644 index 00000000..da14a777 --- /dev/null +++ b/Mastodon/Service/ThemeService/ThemeService+Appearance.swift @@ -0,0 +1,57 @@ +// +// ThemeService+Appearance.swift +// Mastodon +// +// Created by MainasuK Cirno on 2021-7-19. +// + +import UIKit + +extension ThemeService { + func set(themeName: ThemeName) { + UserDefaults.shared.currentThemeNameRawValue = themeName.rawValue + + let theme = themeName.theme + apply(theme: theme) + currentTheme.value = theme + } + + func apply(theme: Theme) { + // set navigation bar appearance + let appearance = UINavigationBarAppearance() + appearance.configureWithDefaultBackground() + appearance.backgroundColor = theme.navigationBarBackgroundColor + UINavigationBar.appearance().standardAppearance = appearance + UINavigationBar.appearance().compactAppearance = appearance + UINavigationBar.appearance().scrollEdgeAppearance = appearance + + // set tab bar appearance + let tabBarAppearance = UITabBarAppearance() + tabBarAppearance.configureWithDefaultBackground() + + let tabBarItemAppearance = UITabBarItemAppearance() + tabBarItemAppearance.selected.iconColor = theme.tabBarItemSelectedIconColor + tabBarItemAppearance.focused.iconColor = theme.tabBarItemFocusedIconColor + tabBarItemAppearance.normal.iconColor = theme.tabBarItemNormalIconColor + tabBarItemAppearance.disabled.iconColor = theme.tabBarItemDisabledIconColor + tabBarAppearance.stackedLayoutAppearance = tabBarItemAppearance + tabBarAppearance.inlineLayoutAppearance = tabBarItemAppearance + tabBarAppearance.compactInlineLayoutAppearance = tabBarItemAppearance + + tabBarAppearance.backgroundColor = theme.tabBarBackgroundColor + tabBarAppearance.selectionIndicatorTintColor = Asset.Colors.brandBlue.color + UITabBar.appearance().standardAppearance = tabBarAppearance + UITabBar.appearance().barTintColor = theme.tabBarBackgroundColor + + // set table view cell appearance + UITableViewCell.appearance().backgroundColor = theme.tableViewCellBackgroundColor + UITableViewCell.appearance(whenContainedInInstancesOf: [SettingsViewController.self]).backgroundColor = theme.secondarySystemGroupedBackgroundColor + UITableViewCell.appearance().selectionColor = theme.tableViewCellSelectionBackgroundColor + + // set search bar appearance + UISearchBar.appearance().tintColor = Asset.Colors.brandBlue.color + UISearchBar.appearance().barTintColor = theme.navigationBarBackgroundColor + let cancelButtonAttributes: [NSAttributedString.Key : Any] = [NSAttributedString.Key.foregroundColor: Asset.Colors.brandBlue.color] + UIBarButtonItem.appearance(whenContainedInInstancesOf: [UISearchBar.self]).setTitleTextAttributes(cancelButtonAttributes, for: .normal) + } +} diff --git a/Mastodon/Service/ThemeService/ThemeService.swift b/Mastodon/Service/ThemeService/ThemeService.swift index c55000b8..35d5b349 100644 --- a/Mastodon/Service/ThemeService/ThemeService.swift +++ b/Mastodon/Service/ThemeService/ThemeService.swift @@ -21,52 +21,4 @@ final class ThemeService { currentTheme = CurrentValueSubject(theme) } - func set(themeName: ThemeName) { - UserDefaults.shared.currentThemeNameRawValue = themeName.rawValue - - let theme = themeName.theme - apply(theme: theme) - currentTheme.value = theme - } - - func apply(theme: Theme) { - // set navigation bar appearance - let appearance = UINavigationBarAppearance() - appearance.configureWithDefaultBackground() - appearance.backgroundColor = theme.navigationBarBackgroundColor - UINavigationBar.appearance().standardAppearance = appearance - UINavigationBar.appearance().compactAppearance = appearance - UINavigationBar.appearance().scrollEdgeAppearance = appearance - - // set tab bar appearance - let tabBarAppearance = UITabBarAppearance() - tabBarAppearance.configureWithDefaultBackground() - - let tabBarItemAppearance = UITabBarItemAppearance() - tabBarItemAppearance.selected.iconColor = theme.tabBarItemSelectedIconColor - tabBarItemAppearance.focused.iconColor = theme.tabBarItemFocusedIconColor - tabBarItemAppearance.normal.iconColor = theme.tabBarItemNormalIconColor - tabBarItemAppearance.disabled.iconColor = theme.tabBarItemDisabledIconColor - tabBarAppearance.stackedLayoutAppearance = tabBarItemAppearance - tabBarAppearance.inlineLayoutAppearance = tabBarItemAppearance - tabBarAppearance.compactInlineLayoutAppearance = tabBarItemAppearance - - tabBarAppearance.backgroundColor = theme.tabBarBackgroundColor - tabBarAppearance.selectionIndicatorTintColor = Asset.Colors.brandBlue.color - UITabBar.appearance().standardAppearance = tabBarAppearance - UITabBar.appearance().barTintColor = theme.tabBarBackgroundColor - - // set table view cell appearance - UITableViewCell.appearance().backgroundColor = theme.tableViewCellBackgroundColor - UITableViewCell.appearance(whenContainedInInstancesOf: [SettingsViewController.self]).backgroundColor = theme.secondarySystemGroupedBackgroundColor - UITableViewCell.appearance().selectionColor = theme.tableViewCellSelectionBackgroundColor - - // set search bar appearance - UISearchBar.appearance().tintColor = Asset.Colors.brandBlue.color - UISearchBar.appearance().barTintColor = theme.navigationBarBackgroundColor - let cancelButtonAttributes: [NSAttributedString.Key : Any] = [NSAttributedString.Key.foregroundColor: Asset.Colors.brandBlue.color] - UIBarButtonItem.appearance(whenContainedInInstancesOf: [UISearchBar.self]).setTitleTextAttributes(cancelButtonAttributes, for: .normal) - } - } - diff --git a/Mastodon/Supporting Files/AppDelegate.swift b/Mastodon/Supporting Files/AppDelegate.swift index 3598e019..56382bab 100644 --- a/Mastodon/Supporting Files/AppDelegate.swift +++ b/Mastodon/Supporting Files/AppDelegate.swift @@ -10,6 +10,7 @@ import UIKit import UserNotifications import AppShared import AVFoundation +@_exported import MastodonUI #if ASDK import AsyncDisplayKit diff --git a/MastodonSDK/Package.swift b/MastodonSDK/Package.swift index 4b3ba9d4..ef5f9313 100644 --- a/MastodonSDK/Package.swift +++ b/MastodonSDK/Package.swift @@ -24,6 +24,8 @@ let package = Package( .package(url: "https://github.com/apple/swift-nio.git", from: "1.0.0"), .package(url: "https://github.com/kean/Nuke.git", from: "10.3.1"), .package(name: "NukeFLAnimatedImagePlugin", url: "https://github.com/kean/Nuke-FLAnimatedImage-Plugin.git", from: "8.0.0"), + .package(name: "UITextView+Placeholder", url: "https://github.com/MainasuK/UITextView-Placeholder.git", from: "1.4.1"), + .package(name: "Introspect", url: "https://github.com/siteline/SwiftUI-Introspect.git", from: "0.1.3") ], targets: [ // Targets are the basic building blocks of a package. A target can define a module or a test suite. @@ -38,9 +40,12 @@ let package = Package( .target( name: "MastodonUI", dependencies: [ + "MastodonSDK", "MastodonExtension", "Nuke", - "NukeFLAnimatedImagePlugin" + "NukeFLAnimatedImagePlugin", + "UITextView+Placeholder", + "Introspect", ] ), .target( diff --git a/Mastodon/Extension/NSLayoutConstraint.swift b/MastodonSDK/Sources/MastodonExtension/NSLayoutConstraint.swift similarity index 67% rename from Mastodon/Extension/NSLayoutConstraint.swift rename to MastodonSDK/Sources/MastodonExtension/NSLayoutConstraint.swift index eea697e2..057b1785 100644 --- a/Mastodon/Extension/NSLayoutConstraint.swift +++ b/MastodonSDK/Sources/MastodonExtension/NSLayoutConstraint.swift @@ -8,12 +8,12 @@ import UIKit extension NSLayoutConstraint { - func priority(_ priority: UILayoutPriority) -> Self { + public func priority(_ priority: UILayoutPriority) -> Self { self.priority = priority return self } - func identifier(_ identifier: String?) -> Self { + public func identifier(_ identifier: String?) -> Self { self.identifier = identifier return self } diff --git a/Mastodon/Extension/UserDefaults.swift b/MastodonSDK/Sources/MastodonExtension/UserDefaults.swift similarity index 81% rename from Mastodon/Extension/UserDefaults.swift rename to MastodonSDK/Sources/MastodonExtension/UserDefaults.swift index 619d6c25..4bba77dc 100644 --- a/Mastodon/Extension/UserDefaults.swift +++ b/MastodonSDK/Sources/MastodonExtension/UserDefaults.swift @@ -6,11 +6,10 @@ // import Foundation -import AppShared extension UserDefaults { - subscript(key: String) -> T? { + public subscript(key: String) -> T? { get { if let rawValue = value(forKey: key) as? T.RawValue { return T(rawValue: rawValue) @@ -20,7 +19,7 @@ extension UserDefaults { set { set(newValue?.rawValue, forKey: key) } } - subscript(key: String) -> T? { + public subscript(key: String) -> T? { get { return value(forKey: key) as? T } set { set(newValue, forKey: key) } } diff --git a/MastodonSDK/Sources/MastodonUI/Compose/ComposeView.swift b/MastodonSDK/Sources/MastodonUI/Compose/ComposeView.swift deleted file mode 100644 index 10ae383d..00000000 --- a/MastodonSDK/Sources/MastodonUI/Compose/ComposeView.swift +++ /dev/null @@ -1,68 +0,0 @@ -// -// ComposeView.swift -// -// -// Created by MainasuK Cirno on 2021-7-16. -// - -import SwiftUI - -public struct ComposeView: View { - - @EnvironmentObject public var viewModel: ComposeViewModel - - public init() { } - - public var body: some View { - GeometryReader { proxy in - ScrollView(.vertical) { - StatusAuthorView( - avatarImageURL: viewModel.avatarImageURL, - name: viewModel.authorName, - username: viewModel.authorUsername - ) - TextEditorView( - string: $viewModel.statusContent, - width: viewModel.frame.width, - attributedString: viewModel.statusContentAttributedString - ) - .frame(width: viewModel.frame.width) - .frame(minHeight: 100) - ForEach(viewModel.attachments, id: \.self) { image in - Image(uiImage: image) - .resizable() - .aspectRatio(16.0/9.0, contentMode: .fill) - .frame(maxWidth: .infinity) - .background(Color.gray) - .cornerRadius(4) - } - } // end ScrollView - .preference( - key: ComposeViewFramePreferenceKey.self, - value: proxy.frame(in: .local) - ) - .onPreferenceChange(ComposeViewFramePreferenceKey.self) { frame in - viewModel.frame = frame - print(frame) - } - } - } -} - -struct ComposeViewFramePreferenceKey: PreferenceKey { - static var defaultValue: CGRect = .zero - static func reduce(value: inout CGRect, nextValue: () -> CGRect) { } -} - -struct ComposeView_Previews: PreviewProvider { - - static let viewModel: ComposeViewModel = { - let viewModel = ComposeViewModel() - return viewModel - }() - - static var previews: some View { - ComposeView().environmentObject(viewModel) - } - -} diff --git a/MastodonSDK/Sources/MastodonUI/Compose/ComposeViewModel.swift b/MastodonSDK/Sources/MastodonUI/Compose/ComposeViewModel.swift deleted file mode 100644 index 4679c61c..00000000 --- a/MastodonSDK/Sources/MastodonUI/Compose/ComposeViewModel.swift +++ /dev/null @@ -1,46 +0,0 @@ -// -// ComposeViewModel.swift -// ShareActionExtension -// -// Created by MainasuK Cirno on 2021-7-16. -// - -import Foundation -import SwiftUI -import Combine - -public class ComposeViewModel: ObservableObject { - - var disposeBag = Set() - - @Published var frame: CGRect = .zero - - @Published var avatarImageURL: URL? - @Published var authorName: String = "" - @Published var authorUsername: String = "" - - @Published var statusContent = "" - @Published var statusContentAttributedString = NSAttributedString() - @Published var contentWarningContent = "" - - @Published var attachments: [UIImage] = [] - - public init() { - $statusContent - .map { NSAttributedString(string: $0) } - .assign(to: &$statusContentAttributedString) - - #if DEBUG - avatarImageURL = URL(string: "https://upload.wikimedia.org/wikipedia/commons/2/2c/Rotating_earth_%28large%29.gif") - authorName = "Alice" - authorUsername = "alice" - attachments = [ - UIImage(systemName: "photo")!, - UIImage(systemName: "photo")!, - UIImage(systemName: "photo")!, - UIImage(systemName: "photo")!, - ] - #endif - } - -} diff --git a/Mastodon/Service/KeyboardResponderService.swift b/MastodonSDK/Sources/MastodonUI/Service/KeyboardResponderService.swift similarity index 90% rename from Mastodon/Service/KeyboardResponderService.swift rename to MastodonSDK/Sources/MastodonUI/Service/KeyboardResponderService.swift index d4bf9b58..65328afa 100644 --- a/Mastodon/Service/KeyboardResponderService.swift +++ b/MastodonSDK/Sources/MastodonUI/Service/KeyboardResponderService.swift @@ -8,7 +8,7 @@ import UIKit import Combine -final class KeyboardResponderService { +final public class KeyboardResponderService { var disposeBag = Set() @@ -16,9 +16,9 @@ final class KeyboardResponderService { public static let shared = KeyboardResponderService() // output - let isShow = CurrentValueSubject(false) - let state = CurrentValueSubject(.none) - let endFrame = CurrentValueSubject(.zero) + public let isShow = CurrentValueSubject(false) + public let state = CurrentValueSubject(.none) + public let endFrame = CurrentValueSubject(.zero) private init() { NotificationCenter.default.publisher(for: UIResponder.keyboardWillShowNotification, object: nil) @@ -82,7 +82,7 @@ extension KeyboardResponderService { } extension KeyboardResponderService { - enum KeyboardState { + public enum KeyboardState { case none case notLocal case notDock // undock | split diff --git a/MastodonSDK/Sources/MastodonUI/AnimatedImage.swift b/MastodonSDK/Sources/MastodonUI/SwiftUI/AnimatedImage.swift similarity index 78% rename from MastodonSDK/Sources/MastodonUI/AnimatedImage.swift rename to MastodonSDK/Sources/MastodonUI/SwiftUI/AnimatedImage.swift index d6c91678..9a0f6510 100644 --- a/MastodonSDK/Sources/MastodonUI/AnimatedImage.swift +++ b/MastodonSDK/Sources/MastodonUI/SwiftUI/AnimatedImage.swift @@ -9,23 +9,27 @@ import SwiftUI import Nuke import FLAnimatedImage -struct AnimatedImage: UIViewRepresentable { +public struct AnimatedImage: UIViewRepresentable { - let imageURL: URL? + public let imageURL: URL? - func makeUIView(context: Context) -> FLAnimatedImageViewProxy { + public init(imageURL: URL?) { + self.imageURL = imageURL + } + + public func makeUIView(context: Context) -> FLAnimatedImageViewProxy { let proxy = FLAnimatedImageViewProxy(frame: .zero) Nuke.loadImage(with: imageURL, into: proxy.imageView) return proxy } - func updateUIView(_ proxy: FLAnimatedImageViewProxy, context: Context) { + public func updateUIView(_ proxy: FLAnimatedImageViewProxy, context: Context) { Nuke.cancelRequest(for: proxy.imageView) Nuke.loadImage(with: imageURL, into: proxy.imageView) } } -final class FLAnimatedImageViewProxy: UIView { +final public class FLAnimatedImageViewProxy: UIView { let imageView = FLAnimatedImageView() override init(frame: CGRect) { diff --git a/Mastodon/Vender/PHPickerResultLoader.swift b/MastodonSDK/Sources/MastodonUI/Vendor/ItemProviderLoader.swift similarity index 74% rename from Mastodon/Vender/PHPickerResultLoader.swift rename to MastodonSDK/Sources/MastodonUI/Vendor/ItemProviderLoader.swift index 8a3bca62..2524fcb1 100644 --- a/Mastodon/Vender/PHPickerResultLoader.swift +++ b/MastodonSDK/Sources/MastodonUI/Vendor/ItemProviderLoader.swift @@ -1,6 +1,6 @@ // -// PHPickerResultLoader.swift -// Mastodon +// ItemProviderLoader.swift +// MastodonUI // // Created by MainasuK Cirno on 2021-3-18. // @@ -14,11 +14,19 @@ import MastodonSDK // load image with low memory usage // Refs: https://christianselig.com/2020/09/phpickerviewcontroller-efficiently/ -enum PHPickerResultLoader { +public enum ItemProviderLoader { + static let logger = Logger(subsystem: "ItemProviderLoader", category: "logic") +} + +extension ItemProviderLoader { + + public static func loadImageData(from result: PHPickerResult) -> Future { + loadImageData(from: result.itemProvider) + } - static func loadImageData(from result: PHPickerResult) -> Future { + public static func loadImageData(from itemProvider: NSItemProvider) -> Future { Future { promise in - result.itemProvider.loadFileRepresentation(forTypeIdentifier: UTType.image.identifier) { url, error in + itemProvider.loadFileRepresentation(forTypeIdentifier: UTType.image.identifier) { url, error in if let error = error { promise(.failure(error)) return @@ -63,17 +71,25 @@ enum PHPickerResultLoader { CGImageDestinationFinalize(imageDestination) let dataSize = ByteCountFormatter.string(fromByteCount: Int64(data.length), countStyle: .memory) - os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: load image %s", ((#file as NSString).lastPathComponent), #line, #function, dataSize) + logger.debug("\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): load image \(dataSize)") let file = Mastodon.Query.MediaAttachment.jpeg(data as Data) promise(.success(file)) } } } - - static func loadVideoData(from result: PHPickerResult) -> Future { + +} + +extension ItemProviderLoader { + + public static func loadVideoData(from result: PHPickerResult) -> Future { + loadVideoData(from: result.itemProvider) + } + + public static func loadVideoData(from itemProvider: NSItemProvider) -> Future { Future { promise in - result.itemProvider.loadFileRepresentation(forTypeIdentifier: UTType.movie.identifier) { url, error in + itemProvider.loadFileRepresentation(forTypeIdentifier: UTType.movie.identifier) { url, error in if let error = error { promise(.failure(error)) return diff --git a/Mastodon/Vender/TwitterTextEditor+String.swift b/MastodonSDK/Sources/MastodonUI/Vendor/TwitterTextEditor+String.swift similarity index 84% rename from Mastodon/Vender/TwitterTextEditor+String.swift rename to MastodonSDK/Sources/MastodonUI/Vendor/TwitterTextEditor+String.swift index 7abdba3a..b6b3926b 100644 --- a/Mastodon/Vender/TwitterTextEditor+String.swift +++ b/MastodonSDK/Sources/MastodonUI/Vendor/TwitterTextEditor+String.swift @@ -10,16 +10,16 @@ import Foundation extension String { @inlinable - var length: Int { + public var length: Int { (self as NSString).length } @inlinable - func substring(with range: NSRange) -> String { + public func substring(with range: NSRange) -> String { (self as NSString).substring(with: range) } - func substring(with result: NSTextCheckingResult, at index: Int) -> String? { + public func substring(with result: NSTextCheckingResult, at index: Int) -> String? { guard index < result.numberOfRanges else { return nil } @@ -30,7 +30,7 @@ extension String { return substring(with: result.range(at: index)) } - func firstMatch(pattern: String, + public func firstMatch(pattern: String, options: NSRegularExpression.Options = [], range: NSRange? = nil) -> NSTextCheckingResult? { @@ -41,7 +41,7 @@ extension String { return regularExpression.firstMatch(in: self, options: [], range: range) } - func matches(pattern: String, + public func matches(pattern: String, options: NSRegularExpression.Options = [], range: NSRange? = nil) -> [NSTextCheckingResult] { diff --git a/Mastodon/Vender/UIViewPreview.swift b/MastodonSDK/Sources/MastodonUI/Vendor/UIViewPreview.swift similarity index 100% rename from Mastodon/Vender/UIViewPreview.swift rename to MastodonSDK/Sources/MastodonUI/Vendor/UIViewPreview.swift diff --git a/Mastodon/Scene/Share/View/Button/HighlightDimmableButton.swift b/MastodonSDK/Sources/MastodonUI/View/Button/HighlightDimmableButton.swift similarity index 59% rename from Mastodon/Scene/Share/View/Button/HighlightDimmableButton.swift rename to MastodonSDK/Sources/MastodonUI/View/Button/HighlightDimmableButton.swift index 5202d376..c956c604 100644 --- a/Mastodon/Scene/Share/View/Button/HighlightDimmableButton.swift +++ b/MastodonSDK/Sources/MastodonUI/View/Button/HighlightDimmableButton.swift @@ -7,25 +7,25 @@ import UIKit -final class HighlightDimmableButton: UIButton { +final public class HighlightDimmableButton: UIButton { - var expandEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0) + public var expandEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0) - override init(frame: CGRect) { + public override init(frame: CGRect) { super.init(frame: frame) _init() } - required init?(coder: NSCoder) { + public required init?(coder: NSCoder) { super.init(coder: coder) _init() } - override func point(inside point: CGPoint, with event: UIEvent?) -> Bool { + public override func point(inside point: CGPoint, with event: UIEvent?) -> Bool { return bounds.inset(by: expandEdgeInsets).contains(point) } - override var isHighlighted: Bool { + public override var isHighlighted: Bool { didSet { alpha = isHighlighted ? 0.6 : 1 } diff --git a/MastodonSDK/Sources/MastodonUI/RoundedEdgesButton.swift b/MastodonSDK/Sources/MastodonUI/View/Button/RoundedEdgesButton.swift similarity index 100% rename from MastodonSDK/Sources/MastodonUI/RoundedEdgesButton.swift rename to MastodonSDK/Sources/MastodonUI/View/Button/RoundedEdgesButton.swift diff --git a/Podfile b/Podfile index ecfb510b..cd7dbfd6 100644 --- a/Podfile +++ b/Podfile @@ -34,6 +34,11 @@ target 'NotificationService' do use_frameworks! end +target 'ShareActionExtension' do + # Comment the next line if you don't want to use dynamic frameworks + use_frameworks! +end + target 'AppShared' do # Comment the next line if you don't want to use dynamic frameworks use_frameworks! diff --git a/Podfile.lock b/Podfile.lock index 079d0080..516d109e 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -78,6 +78,6 @@ SPEC CHECKSUMS: Texture: 2f109e937850d94d1d07232041c9c7313ccddb81 "UITextField+Shake": 298ac5a0f239d731bdab999b19b628c956ca0ac3 -PODFILE CHECKSUM: f2f99b5771c5c36ef69d13999b88cea5b0e8bfe1 +PODFILE CHECKSUM: adf1bf30957525fcafb99001323d1c6ad9995b9d COCOAPODS: 1.10.1 diff --git a/ShareActionExtension/Scene/ShareViewController.swift b/ShareActionExtension/Scene/ShareViewController.swift new file mode 100644 index 00000000..3fba5adb --- /dev/null +++ b/ShareActionExtension/Scene/ShareViewController.swift @@ -0,0 +1,228 @@ +// +// ShareViewController.swift +// MastodonShareAction +// +// Created by MainasuK Cirno on 2021-7-16. +// + +import os.log +import UIKit +import Combine +import MastodonUI +import SwiftUI + +class ShareViewController: UIViewController { + + let logger = Logger(subsystem: "ShareViewController", category: "UI") + + var disposeBag = Set() + let viewModel = ShareViewModel() + + let publishButton: UIButton = { + let button = RoundedEdgesButton(type: .custom) + button.setTitle(L10n.Scene.Compose.composeAction, for: .normal) + button.titleLabel?.font = .systemFont(ofSize: 14, weight: .bold) + button.setBackgroundImage(.placeholder(color: Asset.Colors.brandBlue.color), for: .normal) + button.setBackgroundImage(.placeholder(color: Asset.Colors.brandBlue.color.withAlphaComponent(0.5)), for: .highlighted) + button.setBackgroundImage(.placeholder(color: Asset.Colors.Button.disabled.color), for: .disabled) + button.setTitleColor(.white, for: .normal) + button.contentEdgeInsets = UIEdgeInsets(top: 6, left: 16, bottom: 5, right: 16) // set 28pt height + button.adjustsImageWhenHighlighted = false + return button + }() + + private(set) lazy var cancelBarButtonItem = UIBarButtonItem(title: L10n.Common.Controls.Actions.cancel, style: .plain, target: self, action: #selector(ShareViewController.cancelBarButtonItemPressed(_:))) + private(set) lazy var publishBarButtonItem: UIBarButtonItem = { + let barButtonItem = UIBarButtonItem(customView: publishButton) + barButtonItem.target = self + barButtonItem.action = #selector(ShareViewController.publishBarButtonItemPressed(_:)) + return barButtonItem + }() + + let activityIndicatorBarButtonItem: UIBarButtonItem = { + let indicatorView = UIActivityIndicatorView(style: .medium) + let barButtonItem = UIBarButtonItem(customView: indicatorView) + indicatorView.startAnimating() + return barButtonItem + }() + + + let viewSafeAreaDidChange = PassthroughSubject() + let composeToolbarView = ComposeToolbarView() + var composeToolbarViewBottomLayoutConstraint: NSLayoutConstraint! + let composeToolbarBackgroundView: UIView = { + let view = UIView() + view.backgroundColor = Asset.Scene.Compose.toolbarBackground.color + return view + }() +} + +extension ShareViewController { + + override func viewDidLoad() { + super.viewDidLoad() + + view.backgroundColor = Asset.Colors.Background.systemBackground.color + + navigationItem.leftBarButtonItem = cancelBarButtonItem + viewModel.isBusy + .receive(on: DispatchQueue.main) + .sink { [weak self] isBusy in + guard let self = self else { return } + self.navigationItem.rightBarButtonItem = isBusy ? self.activityIndicatorBarButtonItem : self.publishBarButtonItem + } + .store(in: &disposeBag) + + let hostingViewController = UIHostingController( + rootView: ComposeView().environmentObject(viewModel.composeViewModel) + ) + addChild(hostingViewController) + view.addSubview(hostingViewController.view) + hostingViewController.view.translatesAutoresizingMaskIntoConstraints = false + view.addSubview(hostingViewController.view) + NSLayoutConstraint.activate([ + hostingViewController.view.topAnchor.constraint(equalTo: view.topAnchor), + hostingViewController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor), + hostingViewController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor), + hostingViewController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor), + ]) + hostingViewController.didMove(toParent: self) + + composeToolbarView.translatesAutoresizingMaskIntoConstraints = false + view.addSubview(composeToolbarView) + composeToolbarViewBottomLayoutConstraint = view.bottomAnchor.constraint(equalTo: composeToolbarView.bottomAnchor) + NSLayoutConstraint.activate([ + composeToolbarView.leadingAnchor.constraint(equalTo: view.leadingAnchor), + composeToolbarView.trailingAnchor.constraint(equalTo: view.trailingAnchor), + composeToolbarViewBottomLayoutConstraint, + composeToolbarView.heightAnchor.constraint(equalToConstant: ComposeToolbarView.toolbarHeight), + ]) + composeToolbarView.preservesSuperviewLayoutMargins = true + composeToolbarView.delegate = self + + composeToolbarBackgroundView.translatesAutoresizingMaskIntoConstraints = false + view.insertSubview(composeToolbarBackgroundView, belowSubview: composeToolbarView) + NSLayoutConstraint.activate([ + composeToolbarBackgroundView.topAnchor.constraint(equalTo: composeToolbarView.topAnchor), + composeToolbarBackgroundView.leadingAnchor.constraint(equalTo: composeToolbarView.leadingAnchor), + composeToolbarBackgroundView.trailingAnchor.constraint(equalTo: composeToolbarView.trailingAnchor), + view.bottomAnchor.constraint(equalTo: composeToolbarBackgroundView.bottomAnchor), + ]) + + // FIXME: using iOS 15 toolbar for .keyboard placement + let keyboardEventPublishers = Publishers.CombineLatest3( + KeyboardResponderService.shared.isShow, + KeyboardResponderService.shared.state, + KeyboardResponderService.shared.endFrame + ) + + Publishers.CombineLatest( + keyboardEventPublishers, + viewSafeAreaDidChange + ) + .sink(receiveValue: { [weak self] keyboardEvents, _ in + guard let self = self else { return } + + let (isShow, state, endFrame) = keyboardEvents + guard isShow, state == .dock else { + UIView.animate(withDuration: 0.3) { + self.composeToolbarViewBottomLayoutConstraint.constant = self.view.safeAreaInsets.bottom + self.view.layoutIfNeeded() + } + return + } + // isShow AND dock state + + UIView.animate(withDuration: 0.3) { + self.composeToolbarViewBottomLayoutConstraint.constant = endFrame.height + self.view.layoutIfNeeded() + } + }) + .store(in: &disposeBag) + + // bind visibility toolbar UI + Publishers.CombineLatest( + viewModel.selectedStatusVisibility, + viewModel.traitCollectionDidChangePublisher + ) + .receive(on: DispatchQueue.main) + .sink { [weak self] type, _ in + guard let self = self else { return } + let image = type.image(interfaceStyle: self.traitCollection.userInterfaceStyle) + self.composeToolbarView.visibilityButton.setImage(image, for: .normal) + self.composeToolbarView.activeVisibilityType.value = type + } + .store(in: &disposeBag) + + // bind counter + viewModel.characterCount + .receive(on: DispatchQueue.main) + .sink { [weak self] characterCount in + guard let self = self else { return } + let count = ShareViewModel.composeContentLimit - characterCount + self.composeToolbarView.characterCountLabel.text = "\(count)" + switch count { + case _ where count < 0: + self.composeToolbarView.characterCountLabel.font = .monospacedDigitSystemFont(ofSize: 24, weight: .bold) + self.composeToolbarView.characterCountLabel.textColor = Asset.Colors.danger.color + self.composeToolbarView.characterCountLabel.accessibilityLabel = L10n.Scene.Compose.Accessibility.inputLimitExceedsCount(abs(count)) + default: + self.composeToolbarView.characterCountLabel.font = .monospacedDigitSystemFont(ofSize: 15, weight: .regular) + self.composeToolbarView.characterCountLabel.textColor = Asset.Colors.Label.secondary.color + self.composeToolbarView.characterCountLabel.accessibilityLabel = L10n.Scene.Compose.Accessibility.inputLimitRemainsCount(count) + } + } + .store(in: &disposeBag) + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + viewModel.viewDidAppear.value = true + viewModel.inputItems.value = extensionContext?.inputItems.compactMap { $0 as? NSExtensionItem } ?? [] + } + + override func viewSafeAreaInsetsDidChange() { + super.viewSafeAreaInsetsDidChange() + + viewSafeAreaDidChange.send() + } + + override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { + super.traitCollectionDidChange(previousTraitCollection) + + viewModel.traitCollectionDidChangePublisher.send() + } + +} + +extension ShareViewController { + @objc private func cancelBarButtonItemPressed(_ sender: UIBarButtonItem) { + logger.debug("\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public)") + extensionContext?.cancelRequest(withError: ShareViewModel.ShareError.userCancelShare) + } + + @objc private func publishBarButtonItemPressed(_ sender: UIBarButtonItem) { + logger.debug("\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public)") + } +} + +// MARK - ComposeToolbarViewDelegate +extension ShareViewController: ComposeToolbarViewDelegate { + + func composeToolbarView(_ composeToolbarView: ComposeToolbarView, contentWarningButtonDidPressed sender: UIButton) { + logger.debug("\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public)") + + withAnimation { + viewModel.composeViewModel.isContentWarningComposing.toggle() + } + } + + func composeToolbarView(_ composeToolbarView: ComposeToolbarView, visibilityButtonDidPressed sender: UIButton, visibilitySelectionType type: ComposeToolbarView.VisibilitySelectionType) { + logger.debug("\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public)") + + viewModel.selectedStatusVisibility.value = type + } + + +} diff --git a/ShareActionExtension/ShareViewModel.swift b/ShareActionExtension/Scene/ShareViewModel.swift similarity index 54% rename from ShareActionExtension/ShareViewModel.swift rename to ShareActionExtension/Scene/ShareViewModel.swift index 8384eb12..777e5be5 100644 --- a/ShareActionExtension/ShareViewModel.swift +++ b/ShareActionExtension/Scene/ShareViewModel.swift @@ -11,6 +11,8 @@ import Combine import CoreData import CoreDataStack import MastodonUI +import SwiftUI +import UniformTypeIdentifiers final class ShareViewModel { @@ -18,10 +20,15 @@ final class ShareViewModel { var disposeBag = Set() + static let composeContentLimit: Int = 500 + // input - let viewDidAppear = CurrentValueSubject(false) private var coreDataStack: CoreDataStack? var managedObjectContext: NSManagedObjectContext? + var inputItems = CurrentValueSubject<[NSExtensionItem], Never>([]) + let viewDidAppear = CurrentValueSubject(false) + let traitCollectionDidChangePublisher = CurrentValueSubject(Void()) // use CurrentValueSubject to make initial event emit + let selectedStatusVisibility = CurrentValueSubject(.public) // output let authentication = CurrentValueSubject?, Never>(nil) @@ -29,6 +36,7 @@ final class ShareViewModel { let isBusy = CurrentValueSubject(true) let isValid = CurrentValueSubject(false) let composeViewModel = ComposeViewModel() + let characterCount = CurrentValueSubject(0) init() { viewDidAppear.receive(on: DispatchQueue.main) @@ -40,15 +48,63 @@ final class ShareViewModel { } .store(in: &disposeBag) + Publishers.CombineLatest( + inputItems.removeDuplicates(), + viewDidAppear.removeDuplicates() + ) + .receive(on: DispatchQueue.main) + .sink { [weak self] inputItems, _ in + guard let self = self else { return } + self.parse(inputItems: inputItems) + } + .store(in: &disposeBag) + authentication .map { result in result == nil } .assign(to: \.value, on: isFetchAuthentication) .store(in: &disposeBag) + authentication + .compactMap { result -> Bool? in + guard let result = result else { return nil } + switch result { + case .success(let authentication): + return authentication.user.locked + case .failure: + return nil + } + } + .map { locked -> ComposeToolbarView.VisibilitySelectionType in + locked ? .private : .public + } + .assign(to: \.value, on: selectedStatusVisibility) + .store(in: &disposeBag) + isFetchAuthentication .receive(on: DispatchQueue.main) .assign(to: \.value, on: isBusy) .store(in: &disposeBag) + + composeViewModel.statusPlaceholder = L10n.Scene.Compose.contentInputPlaceholder + composeViewModel.contentWarningPlaceholder = L10n.Scene.Compose.ContentWarning.placeholder + composeViewModel.toolbarHeight = ComposeToolbarView.toolbarHeight + + setupBackgroundColor(theme: ThemeService.shared.currentTheme.value) + ThemeService.shared.currentTheme + .receive(on: DispatchQueue.main) + .sink { [weak self] theme in + guard let self = self else { return } + self.setupBackgroundColor(theme: theme) + } + .store(in: &disposeBag) + + composeViewModel.$characterCount + .assign(to: \.value, on: characterCount) + .store(in: &disposeBag) + } + + private func setupBackgroundColor(theme: Theme) { + composeViewModel.contentWarningBackgroundColor = Color(theme.contentWarningOverlayBackgroundColor) } } @@ -99,3 +155,32 @@ extension ShareViewModel { } } } + +extension ShareViewModel { + func parse(inputItems: [NSExtensionItem]) { + var itemProviders: [NSItemProvider] = [] + + for item in inputItems { + itemProviders.append(contentsOf: item.attachments ?? []) + } + + let _movieProvider = itemProviders.first(where: { provider in + return provider.hasRepresentationConforming(toTypeIdentifier: UTType.movie.identifier, fileOptions: []) + }) + + let imageProviders = itemProviders.filter { provider in + return provider.hasRepresentationConforming(toTypeIdentifier: UTType.image.identifier, fileOptions: []) + } + + if let movieProvider = _movieProvider { + composeViewModel.setupAttachmentViewModels([ + StatusAttachmentViewModel(itemProvider: movieProvider) + ]) + } else { + let viewModels = imageProviders.map { provider in + StatusAttachmentViewModel(itemProvider: provider) + } + composeViewModel.setupAttachmentViewModels(viewModels) + } + } +} diff --git a/ShareActionExtension/Scene/View/ComposeToolbarView.swift b/ShareActionExtension/Scene/View/ComposeToolbarView.swift new file mode 100644 index 00000000..e938ebfa --- /dev/null +++ b/ShareActionExtension/Scene/View/ComposeToolbarView.swift @@ -0,0 +1,246 @@ +// +// ComposeToolbarView.swift +// ShareActionExtension +// +// Created by MainasuK Cirno on 2021-7-19. +// + +import os.log +import UIKit +import Combine +import MastodonSDK +import MastodonUI + +protocol ComposeToolbarViewDelegate: AnyObject { + func composeToolbarView(_ composeToolbarView: ComposeToolbarView, contentWarningButtonDidPressed sender: UIButton) + func composeToolbarView(_ composeToolbarView: ComposeToolbarView, visibilityButtonDidPressed sender: UIButton, visibilitySelectionType type: ComposeToolbarView.VisibilitySelectionType) +} + +final class ComposeToolbarView: UIView { + + var disposeBag = Set() + + static let toolbarButtonSize: CGSize = CGSize(width: 44, height: 44) + static let toolbarHeight: CGFloat = 44 + + weak var delegate: ComposeToolbarViewDelegate? + + let contentWarningButton: UIButton = { + let button = HighlightDimmableButton() + ComposeToolbarView.configureToolbarButtonAppearance(button: button) + button.setImage(UIImage(systemName: "exclamationmark.shield", withConfiguration: UIImage.SymbolConfiguration(pointSize: 20, weight: .regular)), for: .normal) + button.accessibilityLabel = L10n.Scene.Compose.Accessibility.enableContentWarning + return button + }() + + let visibilityButton: UIButton = { + let button = HighlightDimmableButton() + ComposeToolbarView.configureToolbarButtonAppearance(button: button) + button.setImage(UIImage(systemName: "person.3", withConfiguration: UIImage.SymbolConfiguration(pointSize: 15, weight: .medium)), for: .normal) + button.accessibilityLabel = L10n.Scene.Compose.Accessibility.postVisibilityMenu + return button + }() + + let characterCountLabel: UILabel = { + let label = UILabel() + label.font = .systemFont(ofSize: 15, weight: .regular) + label.text = "500" + label.textColor = Asset.Colors.Label.secondary.color + label.accessibilityLabel = L10n.Scene.Compose.Accessibility.inputLimitRemainsCount(500) + return label + }() + + let activeVisibilityType = CurrentValueSubject(.public) + + override init(frame: CGRect) { + super.init(frame: frame) + _init() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + _init() + } + +} + +extension ComposeToolbarView { + + private func _init() { + backgroundColor = Asset.Scene.Compose.toolbarBackground.color + + let stackView = UIStackView() + stackView.axis = .horizontal + stackView.spacing = 0 + stackView.distribution = .fillEqually + stackView.translatesAutoresizingMaskIntoConstraints = false + addSubview(stackView) + NSLayoutConstraint.activate([ + stackView.centerYAnchor.constraint(equalTo: centerYAnchor), + layoutMarginsGuide.leadingAnchor.constraint(equalTo: stackView.leadingAnchor, constant: 8), // tweak button margin offset + ]) + + let buttons = [ + contentWarningButton, + visibilityButton, + ] + buttons.forEach { button in + button.translatesAutoresizingMaskIntoConstraints = false + stackView.addArrangedSubview(button) + NSLayoutConstraint.activate([ + button.widthAnchor.constraint(equalToConstant: 44), + button.heightAnchor.constraint(equalToConstant: 44), + ]) + } + + characterCountLabel.translatesAutoresizingMaskIntoConstraints = false + addSubview(characterCountLabel) + NSLayoutConstraint.activate([ + characterCountLabel.topAnchor.constraint(equalTo: topAnchor), + characterCountLabel.leadingAnchor.constraint(greaterThanOrEqualTo: stackView.trailingAnchor, constant: 8), + characterCountLabel.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor), + characterCountLabel.bottomAnchor.constraint(equalTo: bottomAnchor), + ]) + characterCountLabel.setContentHuggingPriority(.defaultHigh, for: .horizontal) + + contentWarningButton.addTarget(self, action: #selector(ComposeToolbarView.contentWarningButtonDidPressed(_:)), for: .touchUpInside) + visibilityButton.menu = createVisibilityContextMenu(interfaceStyle: traitCollection.userInterfaceStyle) + visibilityButton.showsMenuAsPrimaryAction = true + + updateToolbarButtonUserInterfaceStyle() + + // update menu when selected visibility type changed + activeVisibilityType + .receive(on: RunLoop.main) + .sink { [weak self] type in + guard let self = self else { return } + self.visibilityButton.menu = self.createVisibilityContextMenu(interfaceStyle: self.traitCollection.userInterfaceStyle) + } + .store(in: &disposeBag) + } + + override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { + super.traitCollectionDidChange(previousTraitCollection) + + updateToolbarButtonUserInterfaceStyle() + } + +} + +extension ComposeToolbarView { + enum MediaSelectionType: String { + case camera + case photoLibrary + case browse + } + + enum VisibilitySelectionType: String, CaseIterable { + case `public` + // TODO: remove unlisted option from codebase + // case unlisted + case `private` + case direct + + var title: String { + switch self { + case .public: return L10n.Scene.Compose.Visibility.public + // case .unlisted: return L10n.Scene.Compose.Visibility.unlisted + case .private: return L10n.Scene.Compose.Visibility.private + case .direct: return L10n.Scene.Compose.Visibility.direct + } + } + + func image(interfaceStyle: UIUserInterfaceStyle) -> UIImage { + switch self { + case .public: return UIImage(systemName: "globe", withConfiguration: UIImage.SymbolConfiguration(pointSize: 19, weight: .medium))! + // case .unlisted: return UIImage(systemName: "eye.slash", withConfiguration: UIImage.SymbolConfiguration(pointSize: 18, weight: .regular))! + case .private: + switch interfaceStyle { + case .light: return UIImage(systemName: "person.3", withConfiguration: UIImage.SymbolConfiguration(pointSize: 15, weight: .medium))! + default: return UIImage(systemName: "person.3.fill", withConfiguration: UIImage.SymbolConfiguration(pointSize: 15, weight: .medium))! + } + case .direct: return UIImage(systemName: "at", withConfiguration: UIImage.SymbolConfiguration(pointSize: 19, weight: .regular))! + } + } + + var visibility: Mastodon.Entity.Status.Visibility { + switch self { + case .public: return .public + // case .unlisted: return .unlisted + case .private: return .private + case .direct: return .direct + } + } + } +} + +extension ComposeToolbarView { + + private static func configureToolbarButtonAppearance(button: UIButton) { + button.tintColor = Asset.Colors.brandBlue.color + button.setBackgroundImage(.placeholder(size: ComposeToolbarView.toolbarButtonSize, color: .systemFill), for: .highlighted) + button.layer.masksToBounds = true + button.layer.cornerRadius = 5 + button.layer.cornerCurve = .continuous + } + + private func updateToolbarButtonUserInterfaceStyle() { + switch traitCollection.userInterfaceStyle { + case .light: + contentWarningButton.setImage(UIImage(systemName: "exclamationmark.shield", withConfiguration: UIImage.SymbolConfiguration(pointSize: 20, weight: .regular))!, for: .normal) + + case .dark: + contentWarningButton.setImage(UIImage(systemName: "exclamationmark.shield.fill", withConfiguration: UIImage.SymbolConfiguration(pointSize: 20, weight: .regular))!, for: .normal) + + default: + assertionFailure() + } + + visibilityButton.menu = createVisibilityContextMenu(interfaceStyle: traitCollection.userInterfaceStyle) + } + + private func createVisibilityContextMenu(interfaceStyle: UIUserInterfaceStyle) -> UIMenu { + let children: [UIMenuElement] = VisibilitySelectionType.allCases.map { type in + let state: UIMenuElement.State = activeVisibilityType.value == type ? .on : .off + return UIAction(title: type.title, image: type.image(interfaceStyle: interfaceStyle), identifier: nil, discoverabilityTitle: nil, attributes: [], state: state) { [weak self] action in + guard let self = self else { return } + os_log(.info, "%{public}s[%{public}ld], %{public}s: visibilitySelectionType: %s", ((#file as NSString).lastPathComponent), #line, #function, type.rawValue) + self.delegate?.composeToolbarView(self, visibilityButtonDidPressed: self.visibilityButton, visibilitySelectionType: type) + } + } + return UIMenu(title: "", image: nil, identifier: nil, options: .displayInline, children: children) + } + +} + +extension ComposeToolbarView { + + @objc private func contentWarningButtonDidPressed(_ sender: UIButton) { + os_log(.info, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function) + delegate?.composeToolbarView(self, contentWarningButtonDidPressed: sender) + } + +} + +#if canImport(SwiftUI) && DEBUG +import SwiftUI + +struct ComposeToolbarView_Previews: PreviewProvider { + + static var previews: some View { + UIViewPreview(width: 375) { + let toolbarView = ComposeToolbarView() + toolbarView.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + toolbarView.widthAnchor.constraint(equalToConstant: 375).priority(.defaultHigh), + toolbarView.heightAnchor.constraint(equalToConstant: 64).priority(.defaultHigh), + ]) + return toolbarView + } + .previewLayout(.fixed(width: 375, height: 100)) + } + +} + +#endif + diff --git a/ShareActionExtension/Scene/View/ComposeView.swift b/ShareActionExtension/Scene/View/ComposeView.swift new file mode 100644 index 00000000..65b30913 --- /dev/null +++ b/ShareActionExtension/Scene/View/ComposeView.swift @@ -0,0 +1,117 @@ +// +// ComposeView.swift +// +// +// Created by MainasuK Cirno on 2021-7-16. +// + +import UIKit +import SwiftUI + +public struct ComposeView: View { + + @EnvironmentObject var viewModel: ComposeViewModel + @State var statusEditorViewWidth: CGFloat = .zero + + let horizontalMargin: CGFloat = 20 + + public init() { } + + public var body: some View { + GeometryReader { proxy in + List { + // Content Warning + if viewModel.isContentWarningComposing { + ContentWarningEditorView( + contentWarningContent: $viewModel.contentWarningContent, + placeholder: viewModel.contentWarningPlaceholder + ) + .padding(EdgeInsets(top: 6, leading: horizontalMargin, bottom: 6, trailing: horizontalMargin)) + .background(viewModel.contentWarningBackgroundColor) + .transition(.opacity) + .listRow() + } + + // Author + StatusAuthorView( + avatarImageURL: viewModel.avatarImageURL, + name: viewModel.authorName, + username: viewModel.authorUsername + ) + .padding(EdgeInsets(top: 20, leading: horizontalMargin, bottom: 16, trailing: horizontalMargin)) + .listRow() + + // Editor + StatusEditorView( + string: $viewModel.statusContent, + placeholder: viewModel.statusPlaceholder, + width: statusEditorViewWidth, + attributedString: viewModel.statusContentAttributedString, + keyboardType: .twitter + ) + .frame(width: statusEditorViewWidth) + .frame(minHeight: 100) + .padding(EdgeInsets(top: 0, leading: horizontalMargin, bottom: 0, trailing: horizontalMargin)) + .listRow() + + // Attachments + ForEach(viewModel.attachmentViewModels) { viewModel in + StatusAttachmentView( + image: viewModel.thumbnailImage, + removeButtonAction: { + self.viewModel.removeAttachmentViewModel(viewModel) + } + ) + } + .padding(EdgeInsets(top: 16, leading: horizontalMargin, bottom: 0, trailing: horizontalMargin)) + .fixedSize(horizontal: false, vertical: true) + .listRow() + + // bottom padding + Color.clear + .frame(height: viewModel.toolbarHeight + 20) + .listRow() + } // end List + .introspectTableView(customize: { tableView in + tableView.keyboardDismissMode = .onDrag + tableView.verticalScrollIndicatorInsets.bottom = viewModel.toolbarHeight + }) + .preference( + key: ComposeListViewFramePreferenceKey.self, + value: proxy.frame(in: .local) + ) + .onPreferenceChange(ComposeListViewFramePreferenceKey.self) { frame in + var frame = frame + frame.size.width = frame.width - 2 * horizontalMargin + statusEditorViewWidth = frame.width + } + } + } +} + +struct ComposeListViewFramePreferenceKey: PreferenceKey { + static var defaultValue: CGRect = .zero + static func reduce(value: inout CGRect, nextValue: () -> CGRect) { } +} + +extension View { + func listRow() -> some View { + self.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .leading) + .listRowInsets(EdgeInsets(top: -1, leading: -1, bottom: -1, trailing: -1)) + .background(Color(.systemBackground)) + } +} + + +struct ComposeView_Previews: PreviewProvider { + + static let viewModel: ComposeViewModel = { + let viewModel = ComposeViewModel() + return viewModel + }() + + static var previews: some View { + ComposeView().environmentObject(viewModel) + } + +} diff --git a/ShareActionExtension/Scene/View/ComposeViewModel.swift b/ShareActionExtension/Scene/View/ComposeViewModel.swift new file mode 100644 index 00000000..06db193d --- /dev/null +++ b/ShareActionExtension/Scene/View/ComposeViewModel.swift @@ -0,0 +1,80 @@ +// +// ComposeViewModel.swift +// ShareActionExtension +// +// Created by MainasuK Cirno on 2021-7-16. +// + +import Foundation +import SwiftUI +import Combine + +class ComposeViewModel: ObservableObject { + + var disposeBag = Set() + + @Published var toolbarHeight: CGFloat = 0 + + @Published var avatarImageURL: URL? + @Published var authorName: String = "" + @Published var authorUsername: String = "" + + @Published var statusContent = "" + @Published var statusPlaceholder = "" + @Published var statusContentAttributedString = NSAttributedString() + + @Published var isContentWarningComposing = false + @Published var contentWarningBackgroundColor = Color.secondary + @Published var contentWarningPlaceholder = "" + @Published var contentWarningContent = "" + + @Published private(set) var attachmentViewModels: [StatusAttachmentViewModel] = [] + + @Published var characterCount = 0 + + public init() { + $statusContent + .map { NSAttributedString(string: $0) } + .assign(to: &$statusContentAttributedString) + + Publishers.CombineLatest3( + $statusContent, + $isContentWarningComposing, + $contentWarningContent + ) + .map { statusContent, isContentWarningComposing, contentWarningContent in + var count = statusContent.count + if isContentWarningComposing { + count += contentWarningContent.count + } + return count + } + .assign(to: &$characterCount) + + #if DEBUG + avatarImageURL = URL(string: "https://upload.wikimedia.org/wikipedia/commons/2/2c/Rotating_earth_%28large%29.gif") + authorName = "Alice" + authorUsername = "alice" + #endif + } + +} + +extension ComposeViewModel { + func setupAttachmentViewModels(_ viewModels: [StatusAttachmentViewModel]) { + attachmentViewModels = viewModels + for viewModel in viewModels { + viewModel.objectWillChange.sink { [weak self] _ in + guard let self = self else { return } + self.objectWillChange.send() + } + .store(in: &viewModel.disposeBag) + } + } + + func removeAttachmentViewModel(_ viewModel: StatusAttachmentViewModel) { + if let index = attachmentViewModels.firstIndex(where: { $0 === viewModel }) { + attachmentViewModels.remove(at: index) + } + } +} diff --git a/ShareActionExtension/Scene/View/ContentWarningEditorView.swift b/ShareActionExtension/Scene/View/ContentWarningEditorView.swift new file mode 100644 index 00000000..833c919f --- /dev/null +++ b/ShareActionExtension/Scene/View/ContentWarningEditorView.swift @@ -0,0 +1,48 @@ +// +// ContentWarningEditorView.swift +// +// +// Created by MainasuK Cirno on 2021-7-19. +// + +import SwiftUI +import Introspect + +struct ContentWarningEditorView: View { + + @Binding var contentWarningContent: String + let placeholder: String + let spacing: CGFloat = 11 + + var body: some View { + HStack(alignment: .center, spacing: spacing) { + Image(systemName: "exclamationmark.shield") + .font(.system(size: 30, weight: .regular)) + Text(contentWarningContent.isEmpty ? " " : contentWarningContent) + .opacity(0) + .padding(.all, 8) + .frame(maxWidth: .infinity) + .overlay( + TextEditor(text: $contentWarningContent) + .introspectTextView { textView in + textView.backgroundColor = .clear + textView.placeholder = placeholder + } + ) + } + } +} + +struct ContentWarningEditorView_Previews: PreviewProvider { + + @State static var content = "" + + static var previews: some View { + ContentWarningEditorView( + contentWarningContent: $content, + placeholder: "Write an accurate warning here..." + ) + .previewLayout(.fixed(width: 375, height: 100)) + } +} + diff --git a/ShareActionExtension/Scene/View/StatusAttachmentView.swift b/ShareActionExtension/Scene/View/StatusAttachmentView.swift new file mode 100644 index 00000000..0f52afeb --- /dev/null +++ b/ShareActionExtension/Scene/View/StatusAttachmentView.swift @@ -0,0 +1,65 @@ +// +// StatusAttachmentView.swift +// +// +// Created by MainasuK Cirno on 2021-7-19. +// + +import SwiftUI + +struct StatusAttachmentView: View { + + let image: UIImage? + let removeButtonAction: () -> Void + + var body: some View { + let image = image ?? UIImage.placeholder(color: .systemFill) + Color.clear + .aspectRatio(CGSize(width: 16, height: 9), contentMode: .fill) + .overlay( + Image(uiImage: image) + .resizable() + .aspectRatio(contentMode: .fill) + ) + .background(Color.gray) + .cornerRadius(4) + .badgeView( + Button(action: { + removeButtonAction() + }, label: { + Image(systemName: "minus.circle.fill") + .renderingMode(.original) + .font(.system(size: 22, weight: .bold, design: .default)) + }) + .buttonStyle(BorderlessButtonStyle()) + ) + } +} + +extension View { + func badgeView(_ content: Content) -> some View where Content: View { + overlay( + ZStack { + content + } + .alignmentGuide(.top) { $0.height / 2 } + .alignmentGuide(.trailing) { $0.width / 2 } + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topTrailing) + ) + } +} + + +struct StatusAttachmentView_Previews: PreviewProvider { + static var previews: some View { + ScrollView { + StatusAttachmentView( + image: UIImage(systemName: "photo"), + removeButtonAction: { + // do nothing + } + ) + .padding(20) + } + } +} diff --git a/ShareActionExtension/Scene/View/StatusAttachmentViewModel.swift b/ShareActionExtension/Scene/View/StatusAttachmentViewModel.swift new file mode 100644 index 00000000..ba6a4d51 --- /dev/null +++ b/ShareActionExtension/Scene/View/StatusAttachmentViewModel.swift @@ -0,0 +1,104 @@ +// +// StatusAttachmentViewModel.swift +// ShareActionExtension +// +// Created by MainasuK Cirno on 2021-7-19. +// + +import os.log +import Foundation +import SwiftUI +import Combine +import MastodonSDK +import MastodonUI +import AVFoundation +import MobileCoreServices +import UniformTypeIdentifiers + +final class StatusAttachmentViewModel: ObservableObject, Identifiable { + + let logger = Logger(subsystem: "StatusAttachmentViewModel", category: "logic") + + var disposeBag = Set() + + let id = UUID() + let itemProvider: NSItemProvider + + // input + let file = CurrentValueSubject(nil) + @Published var description = "" + + // output + @Published var thumbnailImage: UIImage? + @Published var error: Error? + + init(itemProvider: NSItemProvider) { + self.itemProvider = itemProvider + + Just(itemProvider) + .receive(on: DispatchQueue.main) + .flatMap { result -> AnyPublisher in + if itemProvider.hasRepresentationConforming(toTypeIdentifier: UTType.image.identifier, fileOptions: []) { + return ItemProviderLoader.loadImageData(from: result).eraseToAnyPublisher() + } + if itemProvider.hasRepresentationConforming(toTypeIdentifier: UTType.movie.identifier, fileOptions: []) { + return ItemProviderLoader.loadVideoData(from: result).eraseToAnyPublisher() + } + return Fail(error: AttachmentError.invalidAttachmentType).eraseToAnyPublisher() + } + .sink { [weak self] completion in + guard let self = self else { return } + switch completion { + case .failure(let error): + self.error = error +// self.uploadStateMachine.enter(UploadState.Fail.self) + case .finished: + break + } + } receiveValue: { [weak self] file in + guard let self = self else { return } + self.file.value = file +// self.uploadStateMachine.enter(UploadState.Initial.self) + } + .store(in: &disposeBag) + + + file + .receive(on: DispatchQueue.main) + .map { file -> UIImage? in + guard let file = file else { + return nil + } + + switch file { + case .jpeg(let data), .png(let data): + return data.flatMap { UIImage(data: $0) } + case .gif: + // TODO: + return nil + case .other(let url, _, _): + guard let url = url, FileManager.default.fileExists(atPath: url.path) else { return nil } + let asset = AVURLAsset(url: url) + let assetImageGenerator = AVAssetImageGenerator(asset: asset) + assetImageGenerator.appliesPreferredTrackTransform = true // fix orientation + do { + let cgImage = try assetImageGenerator.copyCGImage(at: CMTimeMake(value: 0, timescale: 1), actualTime: nil) + let image = UIImage(cgImage: cgImage) + return image + } catch { + self.logger.debug("\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): thumbnail generate fail: \(error.localizedDescription)") + return nil + } + } + } + .assign(to: &$thumbnailImage) + } + +} + +extension StatusAttachmentViewModel { + enum AttachmentError: Error { + case invalidAttachmentType + case attachmentTooLarge + } +} diff --git a/MastodonSDK/Sources/MastodonUI/Compose/StatusAuthorView.swift b/ShareActionExtension/Scene/View/StatusAuthorView.swift similarity index 98% rename from MastodonSDK/Sources/MastodonUI/Compose/StatusAuthorView.swift rename to ShareActionExtension/Scene/View/StatusAuthorView.swift index 110dfb7f..c729bb79 100644 --- a/MastodonSDK/Sources/MastodonUI/Compose/StatusAuthorView.swift +++ b/ShareActionExtension/Scene/View/StatusAuthorView.swift @@ -6,6 +6,7 @@ // import SwiftUI +import MastodonUI import Nuke import NukeFLAnimatedImagePlugin import FLAnimatedImage diff --git a/MastodonSDK/Sources/MastodonUI/Compose/TextEditorView.swift b/ShareActionExtension/Scene/View/StatusEditorView.swift similarity index 77% rename from MastodonSDK/Sources/MastodonUI/Compose/TextEditorView.swift rename to ShareActionExtension/Scene/View/StatusEditorView.swift index 4f087f6b..e6944883 100644 --- a/MastodonSDK/Sources/MastodonUI/Compose/TextEditorView.swift +++ b/ShareActionExtension/Scene/View/StatusEditorView.swift @@ -1,45 +1,50 @@ // -// TextEditorView.swift -// +// StatusEditorView.swift +// // // Created by MainasuK Cirno on 2021-7-16. // import UIKit import SwiftUI +import UITextView_Placeholder -public struct TextEditorView: UIViewRepresentable { +public struct StatusEditorView: UIViewRepresentable { @Binding var string: String - + let placeholder: String let width: CGFloat let attributedString: NSAttributedString + let keyboardType: UIKeyboardType public init( string: Binding, + placeholder: String, width: CGFloat, - attributedString: NSAttributedString + attributedString: NSAttributedString, + keyboardType: UIKeyboardType ) { self._string = string + self.placeholder = placeholder self.width = width self.attributedString = attributedString + self.keyboardType = keyboardType } public func makeUIView(context: Context) -> UITextView { let textView = UITextView(frame: .zero) + textView.placeholder = placeholder textView.isScrollEnabled = false textView.font = .preferredFont(forTextStyle: .body) textView.textColor = .label - + textView.keyboardType = keyboardType textView.delegate = context.coordinator textView.translatesAutoresizingMaskIntoConstraints = false let widthLayoutConstraint = textView.widthAnchor.constraint(equalToConstant: 100) widthLayoutConstraint.priority = .required - 1 context.coordinator.widthLayoutConstraint = widthLayoutConstraint - - return textView } @@ -57,10 +62,10 @@ public struct TextEditorView: UIViewRepresentable { } public class Coordinator: NSObject, UITextViewDelegate { - var parent: TextEditorView + var parent: StatusEditorView var widthLayoutConstraint: NSLayoutConstraint? - init(_ parent: TextEditorView) { + init(_ parent: StatusEditorView) { self.parent = parent } diff --git a/ShareActionExtension/ShareActionExtension.entitlements b/ShareActionExtension/ShareActionExtension.entitlements new file mode 100644 index 00000000..c3bc3f81 --- /dev/null +++ b/ShareActionExtension/ShareActionExtension.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.application-groups + + group.org.joinmastodon.app + + + diff --git a/ShareActionExtension/ShareViewController.swift b/ShareActionExtension/ShareViewController.swift deleted file mode 100644 index e44e3f9f..00000000 --- a/ShareActionExtension/ShareViewController.swift +++ /dev/null @@ -1,107 +0,0 @@ -// -// ShareViewController.swift -// MastodonShareAction -// -// Created by MainasuK Cirno on 2021-7-16. -// - -import os.log -import UIKit -import Combine -import MastodonUI -import SwiftUI - -class ShareViewController: UIViewController { - - let logger = Logger(subsystem: "ShareViewController", category: "UI") - - var disposeBag = Set() - let viewModel = ShareViewModel() - - let publishButton: UIButton = { - let button = RoundedEdgesButton(type: .custom) - button.setTitle(L10n.Scene.Compose.composeAction, for: .normal) - button.titleLabel?.font = .systemFont(ofSize: 14, weight: .bold) - button.setBackgroundImage(.placeholder(color: Asset.Colors.brandBlue.color), for: .normal) - button.setBackgroundImage(.placeholder(color: Asset.Colors.brandBlue.color.withAlphaComponent(0.5)), for: .highlighted) - button.setBackgroundImage(.placeholder(color: Asset.Colors.Button.disabled.color), for: .disabled) - button.setTitleColor(.white, for: .normal) - button.contentEdgeInsets = UIEdgeInsets(top: 6, left: 16, bottom: 5, right: 16) // set 28pt height - button.adjustsImageWhenHighlighted = false - return button - }() - - private(set) lazy var cancelBarButtonItem = UIBarButtonItem(title: L10n.Common.Controls.Actions.cancel, style: .plain, target: self, action: #selector(ShareViewController.cancelBarButtonItemPressed(_:))) - private(set) lazy var publishBarButtonItem: UIBarButtonItem = { - let barButtonItem = UIBarButtonItem(customView: publishButton) - barButtonItem.target = self - barButtonItem.action = #selector(ShareViewController.publishBarButtonItemPressed(_:)) - return barButtonItem - }() - - let activityIndicatorBarButtonItem: UIBarButtonItem = { - let indicatorView = UIActivityIndicatorView(style: .medium) - let barButtonItem = UIBarButtonItem(customView: indicatorView) - indicatorView.startAnimating() - return barButtonItem - }() - -} - -extension ShareViewController { - - override func viewDidLoad() { - super.viewDidLoad() - - view.backgroundColor = Asset.Colors.Background.systemBackground.color - - navigationItem.leftBarButtonItem = cancelBarButtonItem - viewModel.isBusy - .receive(on: DispatchQueue.main) - .sink { [weak self] isBusy in - guard let self = self else { return } - self.navigationItem.rightBarButtonItem = isBusy ? self.activityIndicatorBarButtonItem : self.publishBarButtonItem - } - .store(in: &disposeBag) - - let hostingViewController = UIHostingController( - rootView: ComposeView().environmentObject(viewModel.composeViewModel) - ) - addChild(hostingViewController) - view.addSubview(hostingViewController.view) - hostingViewController.view.translatesAutoresizingMaskIntoConstraints = false - view.addSubview(hostingViewController.view) - NSLayoutConstraint.activate([ - hostingViewController.view.topAnchor.constraint(equalTo: view.topAnchor), - hostingViewController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor), - hostingViewController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor), - hostingViewController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor), - ]) - hostingViewController.didMove(toParent: self) - -// viewModel.authentication -// .receive(on: DispatchQueue.main) -// .sink { [weak self] result in -// guard let self = self else { return } -// } - } - - override func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) - - viewModel.viewDidAppear.value = true -// extensionContext - } - -} - -extension ShareViewController { - @objc private func cancelBarButtonItemPressed(_ sender: UIBarButtonItem) { - logger.debug("\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public)") - extensionContext?.cancelRequest(withError: ShareViewModel.ShareError.userCancelShare) - } - - @objc private func publishBarButtonItemPressed(_ sender: UIBarButtonItem) { - logger.debug("\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public)") - } -} From d2f9828f50a4016af02b7f981a8ec0fe6b39f9f8 Mon Sep 17 00:00:00 2001 From: CMK Date: Tue, 20 Jul 2021 16:40:04 +0800 Subject: [PATCH 04/11] feat: complete upload and publish logic --- Localization/app.json | 2 +- Mastodon.xcodeproj/project.pbxproj | 56 +++++- Mastodon/Extension/CGImage.swift | 154 --------------- Mastodon/Extension/UIImage.swift | 81 -------- Mastodon/Generated/Strings.swift | 6 +- .../Helper/MastodonAuthenticationBox.swift | 17 ++ .../UserProvider/UserProviderFacade.swift | 6 +- .../Resources/ar.lproj/Localizable.strings | 4 +- .../Resources/en.lproj/Localizable.strings | 4 +- Mastodon/Scene/Compose/ComposeViewModel.swift | 30 +-- ...ComposeStatusAttachmentTableViewCell.swift | 6 +- .../Notification/NotificationViewModel.swift | 2 +- .../Profile/Favorite/FavoriteViewModel.swift | 2 +- Mastodon/Scene/Profile/ProfileViewModel.swift | 2 +- .../Scene/Report/ReportViewModel+Data.swift | 2 +- Mastodon/Scene/Report/ReportViewModel.swift | 2 +- .../Scene/Search/Search/SearchViewModel.swift | 4 +- .../Service/APIService/APIService+Block.swift | 6 +- .../APIService/APIService+DomainBlock.swift | 6 +- .../APIService/APIService+Favorite.swift | 4 +- .../APIService/APIService+Filter.swift | 2 +- .../APIService/APIService+Follow.swift | 6 +- .../APIService/APIService+FollowRequest.swift | 4 +- .../APIService+HashtagTimeline.swift | 2 +- .../APIService/APIService+HomeTimeline.swift | 2 +- .../Service/APIService/APIService+Media.swift | 12 +- .../Service/APIService/APIService+Mute.swift | 6 +- .../APIService/APIService+Notification.swift | 4 +- .../Service/APIService/APIService+Poll.swift | 4 +- .../APIService/APIService+Reblog.swift | 2 +- .../APIService/APIService+Recommend.swift | 4 +- .../APIService/APIService+Relationship.swift | 2 +- .../APIService/APIService+Report.swift | 2 +- .../APIService/APIService+Search.swift | 2 +- .../APIService+Status+Publish.swift | 60 ++++++ .../APIService/APIService+Status.swift | 41 +--- .../APIService/APIService+Subscriptions.swift | 4 +- .../APIService/APIService+Thread.swift | 2 +- .../APIService/APIService+UserTimeline.swift | 2 +- Mastodon/Service/APIService/APIService.swift | 1 - Mastodon/Service/AuthenticationService.swift | 21 +-- ...astodonAttachmentService+UploadState.swift | 2 +- .../MastodonAttachmentService.swift | 10 +- Mastodon/Service/NotificationService.swift | 6 +- Mastodon/Service/SettingService.swift | 2 +- .../Service/StatusPrefetchingService.swift | 2 +- .../Sources/MastodonExtension/CGImage.swift | 42 +++++ .../Sources/MastodonExtension/UIImage.swift | 77 +++++++- .../Vendor/ItemProviderLoader.swift | 8 +- .../Scene/ShareViewController.swift | 47 ++++- .../Scene/ShareViewModel.swift | 177 +++++++++++++++++- .../Scene/View/ComposeView.swift | 23 ++- .../Scene/View/ComposeViewModel.swift | 55 +++++- .../Scene/View/StatusAttachmentView.swift | 99 ++++++++-- ...tatusAttachmentViewModel+UploadState.swift | 129 +++++++++++++ .../View/StatusAttachmentViewModel.swift | 121 +++++++++++- .../Scene/View/StatusAuthorView.swift | 3 +- .../Scene/View/StatusEditorView.swift | 12 +- ShareActionExtension/Service/APIService.swift | 32 ++++ 59 files changed, 1003 insertions(+), 425 deletions(-) delete mode 100644 Mastodon/Extension/CGImage.swift delete mode 100644 Mastodon/Extension/UIImage.swift create mode 100644 Mastodon/Helper/MastodonAuthenticationBox.swift create mode 100644 Mastodon/Service/APIService/APIService+Status+Publish.swift create mode 100644 MastodonSDK/Sources/MastodonExtension/CGImage.swift create mode 100644 ShareActionExtension/Scene/View/StatusAttachmentViewModel+UploadState.swift create mode 100644 ShareActionExtension/Service/APIService.swift diff --git a/Localization/app.json b/Localization/app.json index 6275742b..f1024620 100644 --- a/Localization/app.json +++ b/Localization/app.json @@ -22,7 +22,7 @@ "publish_post_failure": { "title": "Publish Failure", "message": "Failed to publish the post.\nPlease check your internet connection.", - "attchments_message": { + "attachments_message": { "video_attach_with_photo": "Cannot attach a video to a post that already contains images.", "more_than_one_video": "Cannot attach more than one video." } diff --git a/Mastodon.xcodeproj/project.pbxproj b/Mastodon.xcodeproj/project.pbxproj index ea21a7df..b545bc13 100644 --- a/Mastodon.xcodeproj/project.pbxproj +++ b/Mastodon.xcodeproj/project.pbxproj @@ -39,7 +39,6 @@ 2D198643261BF09500F0B013 /* SearchResultItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D198642261BF09500F0B013 /* SearchResultItem.swift */; }; 2D198649261C0B8500F0B013 /* SearchResultSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D198648261C0B8500F0B013 /* SearchResultSection.swift */; }; 2D206B7225F5D27F00143C56 /* AudioContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D206B7125F5D27F00143C56 /* AudioContainerView.swift */; }; - 2D206B8025F5F45E00143C56 /* UIImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D206B7F25F5F45E00143C56 /* UIImage.swift */; }; 2D206B8625F5FB0900143C56 /* Double.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D206B8525F5FB0900143C56 /* Double.swift */; }; 2D206B8C25F6015000143C56 /* AudioPlaybackService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D206B8B25F6015000143C56 /* AudioPlaybackService.swift */; }; 2D206B9225F60EA700143C56 /* UIControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D206B9125F60EA700143C56 /* UIControl.swift */; }; @@ -533,7 +532,6 @@ DBCBED1D26132E1A00B49291 /* StatusFetchedResultsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBCBED1C26132E1A00B49291 /* StatusFetchedResultsController.swift */; }; DBCC3B30261440A50045B23D /* UITabBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBCC3B2F261440A50045B23D /* UITabBarController.swift */; }; DBCC3B36261440BA0045B23D /* UINavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBCC3B35261440BA0045B23D /* UINavigationController.swift */; }; - DBCC3B89261454BA0045B23D /* CGImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBCC3B88261454BA0045B23D /* CGImage.swift */; }; DBCC3B8F26148F7B0045B23D /* CachedProfileViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBCC3B8E26148F7B0045B23D /* CachedProfileViewModel.swift */; }; DBCC3B9526157E6E0045B23D /* APIService+Relationship.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBCC3B9426157E6E0045B23D /* APIService+Relationship.swift */; }; DBCC3B9B261584A00045B23D /* PrivateNote.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBCC3B9A2615849F0045B23D /* PrivateNote.swift */; }; @@ -569,6 +567,19 @@ DBFEF05F26A57715006D7ED1 /* StatusAuthorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBFEF05926A576EE006D7ED1 /* StatusAuthorView.swift */; }; DBFEF06026A57715006D7ED1 /* StatusAttachmentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBFEF05A26A576EE006D7ED1 /* StatusAttachmentView.swift */; }; DBFEF06326A577F2006D7ED1 /* StatusAttachmentViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBFEF06226A577F2006D7ED1 /* StatusAttachmentViewModel.swift */; }; + DBFEF06826A67DEE006D7ED1 /* MastodonUser.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB45FAE225CA7181005A8AC7 /* MastodonUser.swift */; }; + DBFEF06926A67E45006D7ED1 /* AppearancePreference.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6D1B3C2636857500ACB481 /* AppearancePreference.swift */; }; + DBFEF06A26A67E53006D7ED1 /* Emojis.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBAFB7342645463500371D5F /* Emojis.swift */; }; + DBFEF06B26A67E58006D7ED1 /* Fields.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBA94439265CC0FC00C537E1 /* Fields.swift */; }; + DBFEF06D26A67FB7006D7ED1 /* StatusAttachmentViewModel+UploadState.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBFEF06C26A67FB7006D7ED1 /* StatusAttachmentViewModel+UploadState.swift */; }; + DBFEF06F26A690C4006D7ED1 /* APIService+APIError.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB98337E25C9452D00AD9700 /* APIService+APIError.swift */; }; + DBFEF07126A690E8006D7ED1 /* AlamofireNetworkActivityIndicator in Frameworks */ = {isa = PBXBuildFile; productRef = DBFEF07026A690E8006D7ED1 /* AlamofireNetworkActivityIndicator */; }; + DBFEF07326A6913D006D7ED1 /* APIService.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBFEF07226A6913D006D7ED1 /* APIService.swift */; }; + DBFEF07526A69192006D7ED1 /* APIService+Media.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB9A488F26035963008B817C /* APIService+Media.swift */; }; + DBFEF07726A691FB006D7ED1 /* MastodonAuthenticationBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBFEF07626A691FB006D7ED1 /* MastodonAuthenticationBox.swift */; }; + DBFEF07826A69209006D7ED1 /* MastodonAuthenticationBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBFEF07626A691FB006D7ED1 /* MastodonAuthenticationBox.swift */; }; + DBFEF07B26A6BCE8006D7ED1 /* APIService+Status+Publish.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBFEF07A26A6BCE8006D7ED1 /* APIService+Status+Publish.swift */; }; + DBFEF07C26A6BD0A006D7ED1 /* APIService+Status+Publish.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBFEF07A26A6BCE8006D7ED1 /* APIService+Status+Publish.swift */; }; EE93E8E8F9E0C39EAAEBD92F /* Pods_AppShared.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F4A2A2D7000E477CA459ADA9 /* Pods_AppShared.framework */; }; /* End PBXBuildFile section */ @@ -727,7 +738,6 @@ 2D198642261BF09500F0B013 /* SearchResultItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchResultItem.swift; sourceTree = ""; }; 2D198648261C0B8500F0B013 /* SearchResultSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchResultSection.swift; sourceTree = ""; }; 2D206B7125F5D27F00143C56 /* AudioContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioContainerView.swift; sourceTree = ""; }; - 2D206B7F25F5F45E00143C56 /* UIImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIImage.swift; sourceTree = ""; }; 2D206B8525F5FB0900143C56 /* Double.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Double.swift; sourceTree = ""; }; 2D206B8B25F6015000143C56 /* AudioPlaybackService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioPlaybackService.swift; sourceTree = ""; }; 2D206B9125F60EA700143C56 /* UIControl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIControl.swift; sourceTree = ""; }; @@ -1204,7 +1214,6 @@ DBCBED1C26132E1A00B49291 /* StatusFetchedResultsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusFetchedResultsController.swift; sourceTree = ""; }; DBCC3B2F261440A50045B23D /* UITabBarController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITabBarController.swift; sourceTree = ""; }; DBCC3B35261440BA0045B23D /* UINavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UINavigationController.swift; sourceTree = ""; }; - DBCC3B88261454BA0045B23D /* CGImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CGImage.swift; sourceTree = ""; }; DBCC3B8E26148F7B0045B23D /* CachedProfileViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CachedProfileViewModel.swift; sourceTree = ""; }; DBCC3B9426157E6E0045B23D /* APIService+Relationship.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Relationship.swift"; sourceTree = ""; }; DBCC3B9A2615849F0045B23D /* PrivateNote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivateNote.swift; sourceTree = ""; }; @@ -1242,6 +1251,10 @@ DBFEF05A26A576EE006D7ED1 /* StatusAttachmentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusAttachmentView.swift; sourceTree = ""; }; DBFEF06226A577F2006D7ED1 /* StatusAttachmentViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusAttachmentViewModel.swift; sourceTree = ""; }; DBFEF06726A58D07006D7ED1 /* ShareActionExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = ShareActionExtension.entitlements; sourceTree = ""; }; + DBFEF06C26A67FB7006D7ED1 /* StatusAttachmentViewModel+UploadState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StatusAttachmentViewModel+UploadState.swift"; sourceTree = ""; }; + DBFEF07226A6913D006D7ED1 /* APIService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APIService.swift; sourceTree = ""; }; + DBFEF07626A691FB006D7ED1 /* MastodonAuthenticationBox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonAuthenticationBox.swift; sourceTree = ""; }; + DBFEF07A26A6BCE8006D7ED1 /* APIService+Status+Publish.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Status+Publish.swift"; sourceTree = ""; }; DDB1B139FA8EA26F510D58B6 /* Pods-AppShared.asdk - release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AppShared.asdk - release.xcconfig"; path = "Target Support Files/Pods-AppShared/Pods-AppShared.asdk - release.xcconfig"; sourceTree = ""; }; E5C7236E58D14A0322FE00F2 /* Pods-Mastodon-MastodonUITests.asdk - debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Mastodon-MastodonUITests.asdk - debug.xcconfig"; path = "Target Support Files/Pods-Mastodon-MastodonUITests/Pods-Mastodon-MastodonUITests.asdk - debug.xcconfig"; sourceTree = ""; }; EC6E707B68A67DB08EC288FA /* Pods-MastodonTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MastodonTests.debug.xcconfig"; path = "Target Support Files/Pods-MastodonTests/Pods-MastodonTests.debug.xcconfig"; sourceTree = ""; }; @@ -1341,6 +1354,7 @@ DB41ED8026A54D7C00F58330 /* AlamofireImage in Frameworks */, DBC6463326A195DB00B0E31B /* AppShared.framework in Frameworks */, 4278334D6033AEEE0A1C5155 /* Pods_ShareActionExtension.framework in Frameworks */, + DBFEF07126A690E8006D7ED1 /* AlamofireNetworkActivityIndicator in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2055,6 +2069,7 @@ DB0AC6FB25CD02E600D75117 /* APIService+Instance.swift */, DB59F10D25EF724F001F1DAB /* APIService+Poll.swift */, DB71FD5125F8CCAA00512AE1 /* APIService+Status.swift */, + DBFEF07A26A6BCE8006D7ED1 /* APIService+Status+Publish.swift */, DB938F1426241FDF00E5B6C1 /* APIService+Thread.swift */, DB49A62A25FF36C700B98345 /* APIService+CustomEmoji.swift */, 2D61254C262547C200299647 /* APIService+Notification.swift */, @@ -2497,8 +2512,6 @@ DBD376B1269302A4007FEC24 /* UITableViewCell.swift */, 0FAA101B25E10E760017CCDE /* UIFont.swift */, 2D939AB425EDD8A90076FA61 /* String.swift */, - 2D206B7F25F5F45E00143C56 /* UIImage.swift */, - DBCC3B88261454BA0045B23D /* CGImage.swift */, 2D206B8525F5FB0900143C56 /* Double.swift */, 2D206B9125F60EA700143C56 /* UIControl.swift */, 5DF1056325F887CB00D6C0D4 /* AVPlayer.swift */, @@ -2764,6 +2777,7 @@ DBBC24D926A54BCB00398BB9 /* MastodonStatusContent+ParseResult.swift */, DBBC24DA26A54BCB00398BB9 /* MastodonField.swift */, DBBC24DB26A54BCB00398BB9 /* MastodonStatusContent+Appearance.swift */, + DBFEF07626A691FB006D7ED1 /* MastodonAuthenticationBox.swift */, ); path = Helper; sourceTree = ""; @@ -2791,6 +2805,7 @@ DBC6461926A170AB00B0E31B /* Info.plist */, DBC6461626A170AB00B0E31B /* MainInterface.storyboard */, DBFEF06126A57721006D7ED1 /* Scene */, + DBFEF07426A69140006D7ED1 /* Service */, ); path = ShareActionExtension; sourceTree = ""; @@ -2902,6 +2917,7 @@ DBFEF05926A576EE006D7ED1 /* StatusAuthorView.swift */, DBFEF05A26A576EE006D7ED1 /* StatusAttachmentView.swift */, DBFEF06226A577F2006D7ED1 /* StatusAttachmentViewModel.swift */, + DBFEF06C26A67FB7006D7ED1 /* StatusAttachmentViewModel+UploadState.swift */, ); path = View; sourceTree = ""; @@ -2916,6 +2932,14 @@ path = Scene; sourceTree = ""; }; + DBFEF07426A69140006D7ED1 /* Service */ = { + isa = PBXGroup; + children = ( + DBFEF07226A6913D006D7ED1 /* APIService.swift */, + ); + path = Service; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ @@ -3111,6 +3135,7 @@ DB41ED7F26A54D7C00F58330 /* AlamofireImage */, DB41ED8126A54D8A00F58330 /* MastodonMeta */, DB41ED8326A54D8A00F58330 /* MetaTextView */, + DBFEF07026A690E8006D7ED1 /* AlamofireNetworkActivityIndicator */, ); productName = ShareActionExtension; productReference = DBC6461226A170AB00B0E31B /* ShareActionExtension.appex */; @@ -3821,6 +3846,7 @@ DB36679D268AB91B0027D07F /* ComposeStatusAttachmentTableViewCell.swift in Sources */, DB98336B25C9420100AD9700 /* APIService+App.swift in Sources */, DBBC24E026A54BCB00398BB9 /* MastodonField.swift in Sources */, + DBFEF07B26A6BCE8006D7ED1 /* APIService+Status+Publish.swift in Sources */, DBCBCC032680AF6E000F5B51 /* AsyncHomeTimelineViewController+DebugAction.swift in Sources */, DBA0A11325FB3FC10079C110 /* ComposeToolbarView.swift in Sources */, 2D32EADA25CBCC3300C9ED86 /* PublicTimelineViewModel+LoadMiddleState.swift in Sources */, @@ -3836,7 +3862,6 @@ 2D34D9E226149C920081BFC0 /* SearchRecommendTagsCollectionViewCell.swift in Sources */, 2D76317D25C14DF500929FB9 /* PublicTimelineViewController+Provider.swift in Sources */, 0F20223326145E51000C64BF /* HashtagTimelineViewModel+LoadMiddleState.swift in Sources */, - 2D206B8025F5F45E00143C56 /* UIImage.swift in Sources */, DB6D9F502635761F008423CD /* SubscriptionAlerts.swift in Sources */, 0F20220726134DA4000C64BF /* HashtagTimelineViewModel+Diffable.swift in Sources */, DBE54AC62636C89F004E7C0B /* NotificationPreference.swift in Sources */, @@ -3871,6 +3896,7 @@ DBBC24C026A5443100398BB9 /* MastodonTheme.swift in Sources */, DBBC24B526A540AE00398BB9 /* AvatarConfigurableView.swift in Sources */, DB9A489026035963008B817C /* APIService+Media.swift in Sources */, + DBFEF07726A691FB006D7ED1 /* MastodonAuthenticationBox.swift in Sources */, DBBC24CF26A547AE00398BB9 /* ThemeService+Appearance.swift in Sources */, 2D198649261C0B8500F0B013 /* SearchResultSection.swift in Sources */, DB4F097B26A039FF00D62E92 /* SearchHistorySection.swift in Sources */, @@ -3910,7 +3936,6 @@ 2D6DE40026141DF600A63F6A /* SearchViewModel.swift in Sources */, DB51D172262832380062B7A1 /* BlurHashDecode.swift in Sources */, DBAFB7352645463500371D5F /* Emojis.swift in Sources */, - DBCC3B89261454BA0045B23D /* CGImage.swift in Sources */, DBCCC71E25F73297007E1AB6 /* APIService+Reblog.swift in Sources */, DBE3CE13261D7D4200430CC6 /* StatusTableViewControllerAspect.swift in Sources */, 5BB04FD5262E7AFF0043BFF6 /* ReportViewController.swift in Sources */, @@ -3993,21 +4018,28 @@ buildActionMask = 2147483647; files = ( DBFEF05E26A57715006D7ED1 /* ComposeView.swift in Sources */, + DBFEF07326A6913D006D7ED1 /* APIService.swift in Sources */, DB41ED7C26A54D5500F58330 /* MastodonStatusContent+Appearance.swift in Sources */, DBFEF06026A57715006D7ED1 /* StatusAttachmentView.swift in Sources */, DBC6462326A1712000B0E31B /* ShareViewModel.swift in Sources */, DBBC24B326A53EE700398BB9 /* ActiveLabel.swift in Sources */, + DBFEF06D26A67FB7006D7ED1 /* StatusAttachmentViewModel+UploadState.swift in Sources */, DBBC24CB26A546C000398BB9 /* ThemePreference.swift in Sources */, DBFEF05F26A57715006D7ED1 /* StatusAuthorView.swift in Sources */, DBFEF05D26A57715006D7ED1 /* ContentWarningEditorView.swift in Sources */, + DBFEF06A26A67E53006D7ED1 /* Emojis.swift in Sources */, + DBFEF07526A69192006D7ED1 /* APIService+Media.swift in Sources */, DB41ED7B26A54D4D00F58330 /* MastodonStatusContent+ParseResult.swift in Sources */, DB41ED8A26A54F4C00F58330 /* AttachmentContainerView.swift in Sources */, + DBFEF06F26A690C4006D7ED1 /* APIService+APIError.swift in Sources */, DBFEF05C26A57715006D7ED1 /* StatusEditorView.swift in Sources */, DBBC24C726A5456400398BB9 /* SystemTheme.swift in Sources */, DBBC24B626A5419700398BB9 /* ComposeStatusContentTableViewCell.swift in Sources */, DBC6462926A1736700B0E31B /* Strings.swift in Sources */, DBBC24C826A5456400398BB9 /* ThemeService.swift in Sources */, + DBFEF06826A67DEE006D7ED1 /* MastodonUser.swift in Sources */, DBBC24C926A5456400398BB9 /* MastodonTheme.swift in Sources */, + DBFEF07C26A6BD0A006D7ED1 /* APIService+Status+Publish.swift in Sources */, DBBC24B926A5426000398BB9 /* StatusContentWarningEditorView.swift in Sources */, DB41ED8B26A54F5800F58330 /* AttachmentContainerView+EmptyStateView.swift in Sources */, DBBC24A826A52F9000398BB9 /* ComposeToolbarView.swift in Sources */, @@ -4019,6 +4051,9 @@ DB41ED8926A54F4000F58330 /* ComposeStatusAttachmentCollectionViewCell.swift in Sources */, DBBC24D226A5488600398BB9 /* AvatarConfigurableView.swift in Sources */, DBC6462C26A176B000B0E31B /* Assets.swift in Sources */, + DBFEF06926A67E45006D7ED1 /* AppearancePreference.swift in Sources */, + DBFEF06B26A67E58006D7ED1 /* Fields.swift in Sources */, + DBFEF07826A69209006D7ED1 /* MastodonAuthenticationBox.swift in Sources */, DBC6461526A170AB00B0E31B /* ShareViewController.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -5579,6 +5614,11 @@ package = DBF7A0FA26830C33004176A2 /* XCRemoteSwiftPackageReference "FPSIndicator" */; productName = FPSIndicator; }; + DBFEF07026A690E8006D7ED1 /* AlamofireNetworkActivityIndicator */ = { + isa = XCSwiftPackageProductDependency; + package = 2D61336725C18A4F00CAE157 /* XCRemoteSwiftPackageReference "AlamofireNetworkActivityIndicator" */; + productName = AlamofireNetworkActivityIndicator; + }; /* End XCSwiftPackageProductDependency section */ /* Begin XCVersionGroup section */ diff --git a/Mastodon/Extension/CGImage.swift b/Mastodon/Extension/CGImage.swift deleted file mode 100644 index cced4abe..00000000 --- a/Mastodon/Extension/CGImage.swift +++ /dev/null @@ -1,154 +0,0 @@ -// -// CGImage.swift -// Mastodon -// -// Created by MainasuK Cirno on 2021-3-31. -// - -import CoreImage - -extension CGImage { - // Reference - // https://www.itu.int/dms_pubrec/itu-r/rec/bt/R-REC-BT.709-6-201506-I!!PDF-E.pdf - // Luma Y = 0.2126R + 0.7152G + 0.0722B - var brightness: CGFloat? { - let context = CIContext() // default with metal accelerate - let ciImage = CIImage(cgImage: self) - let rec709Image = context.createCGImage( - ciImage, - from: ciImage.extent, - format: .RGBA8, - colorSpace: CGColorSpace(name: CGColorSpace.itur_709) // BT.709 a.k.a Rec.709 - ) - guard let image = rec709Image, - image.bitsPerPixel == 32, - let data = rec709Image?.dataProvider?.data, - let pointer = CFDataGetBytePtr(data) else { return nil } - - let length = CFDataGetLength(data) - guard length > 0 else { return nil } - - var luma: CGFloat = 0.0 - for i in stride(from: 0, to: length, by: 4) { - let r = pointer[i] - let g = pointer[i + 1] - let b = pointer[i + 2] - let Y = 0.2126 * CGFloat(r) + 0.7152 * CGFloat(g) + 0.0722 * CGFloat(b) - luma += Y - } - luma /= CGFloat(width * height) - return luma - } -} - -#if canImport(SwiftUI) && DEBUG -import SwiftUI -import UIKit - -class BrightnessView: UIView { - let label = UILabel() - let imageView = UIImageView() - - override init(frame: CGRect) { - super.init(frame: frame) - - let stackView = UIStackView() - stackView.axis = .horizontal - stackView.translatesAutoresizingMaskIntoConstraints = false - addSubview(stackView) - NSLayoutConstraint.activate([ - stackView.topAnchor.constraint(equalTo: topAnchor), - stackView.leadingAnchor.constraint(equalTo: leadingAnchor), - stackView.trailingAnchor.constraint(equalTo: trailingAnchor), - stackView.bottomAnchor.constraint(equalTo: bottomAnchor), - ]) - stackView.distribution = .fillEqually - stackView.addArrangedSubview(imageView) - stackView.addArrangedSubview(label) - - imageView.contentMode = .scaleAspectFill - imageView.layer.masksToBounds = true - label.textAlignment = .center - label.numberOfLines = 0 - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - func setImage(_ image: UIImage) { - imageView.image = image - - guard let brightness = image.cgImage?.brightness, - let style = image.domainLumaCoefficientsStyle else { - label.text = "" - return - } - let styleDescription: String = { - switch style { - case .light: return "Light" - case .dark: return "Dark" - case .unspecified: fallthrough - @unknown default: - return "Unknown" - } - }() - - label.text = styleDescription + "\n" + "\(brightness)" - } -} - -struct CGImage_Brightness_Previews: PreviewProvider { - - static var previews: some View { - Group { - UIViewPreview(width: 375) { - let view = BrightnessView() - view.setImage(.placeholder(color: .black)) - return view - } - .previewLayout(.fixed(width: 375, height: 44)) - UIViewPreview(width: 375) { - let view = BrightnessView() - view.setImage(.placeholder(color: .gray)) - return view - } - .previewLayout(.fixed(width: 375, height: 44)) - UIViewPreview(width: 375) { - let view = BrightnessView() - view.setImage(.placeholder(color: .separator)) - return view - } - .previewLayout(.fixed(width: 375, height: 44)) - UIViewPreview(width: 375) { - let view = BrightnessView() - view.setImage(.placeholder(color: .red)) - return view - } - .previewLayout(.fixed(width: 375, height: 44)) - UIViewPreview(width: 375) { - let view = BrightnessView() - view.setImage(.placeholder(color: .green)) - return view - } - .previewLayout(.fixed(width: 375, height: 44)) - UIViewPreview(width: 375) { - let view = BrightnessView() - view.setImage(.placeholder(color: .blue)) - return view - } - .previewLayout(.fixed(width: 375, height: 44)) - UIViewPreview(width: 375) { - let view = BrightnessView() - view.setImage(.placeholder(color: .secondarySystemGroupedBackground)) - return view - } - .previewLayout(.fixed(width: 375, height: 44)) - } - } - -} - -#endif - - diff --git a/Mastodon/Extension/UIImage.swift b/Mastodon/Extension/UIImage.swift deleted file mode 100644 index 7054661b..00000000 --- a/Mastodon/Extension/UIImage.swift +++ /dev/null @@ -1,81 +0,0 @@ -// -// UIImage.swift -// Mastodon -// -// Created by sxiaojian on 2021/3/8. -// - -import CoreImage -import CoreImage.CIFilterBuiltins -import UIKit - -// refs: https://www.hackingwithswift.com/example-code/media/how-to-read-the-average-color-of-a-uiimage-using-ciareaaverage -extension UIImage { - @available(iOS 14.0, *) - var dominantColor: UIColor? { - guard let inputImage = CIImage(image: self) else { return nil } - - let filter = CIFilter.areaAverage() - filter.inputImage = inputImage - filter.extent = inputImage.extent - guard let outputImage = filter.outputImage else { return nil } - - var bitmap = [UInt8](repeating: 0, count: 4) - let context = CIContext(options: [.workingColorSpace: kCFNull]) - context.render(outputImage, toBitmap: &bitmap, rowBytes: 4, bounds: CGRect(x: 0, y: 0, width: 1, height: 1), format: .RGBA8, colorSpace: nil) - - return UIColor(red: CGFloat(bitmap[0]) / 255, green: CGFloat(bitmap[1]) / 255, blue: CGFloat(bitmap[2]) / 255, alpha: CGFloat(bitmap[3]) / 255) - } -} - -extension UIImage { - var domainLumaCoefficientsStyle: UIUserInterfaceStyle? { - guard let brightness = cgImage?.brightness else { return nil } - return brightness > 100 ? .light : .dark // 0 ~ 255 - } -} - -extension UIImage { - func blur(radius: CGFloat) -> UIImage? { - guard let inputImage = CIImage(image: self) else { return nil } - let blurFilter = CIFilter.gaussianBlur() - blurFilter.inputImage = inputImage - blurFilter.radius = Float(radius) - guard let outputImage = blurFilter.outputImage else { return nil } - guard let cgImage = CIContext().createCGImage(outputImage, from: outputImage.extent) else { return nil } - let image = UIImage(cgImage: cgImage, scale: scale, orientation: imageOrientation) - return image - } -} - -extension UIImage { - func withRoundedCorners(radius: CGFloat? = nil) -> UIImage? { - let maxRadius = min(size.width, size.height) / 2 - let cornerRadius: CGFloat = { - guard let radius = radius, radius > 0 else { return maxRadius } - return min(radius, maxRadius) - }() - - let render = UIGraphicsImageRenderer(size: size) - return render.image { (_: UIGraphicsImageRendererContext) in - let rect = CGRect(origin: .zero, size: size) - UIBezierPath(roundedRect: rect, cornerRadius: cornerRadius).addClip() - draw(in: rect) - } - } -} - -extension UIImage { - static func adaptiveUserInterfaceStyleImage(lightImage: UIImage, darkImage: UIImage) -> UIImage { - let imageAsset = UIImageAsset() - imageAsset.register(lightImage, with: UITraitCollection(traitsFrom: [ - UITraitCollection(displayScale: 1.0), - UITraitCollection(userInterfaceStyle: .light) - ])) - imageAsset.register(darkImage, with: UITraitCollection(traitsFrom: [ - UITraitCollection(displayScale: 1.0), - UITraitCollection(userInterfaceStyle: .dark) - ])) - return imageAsset.image(with: UITraitCollection.current) - } -} diff --git a/Mastodon/Generated/Strings.swift b/Mastodon/Generated/Strings.swift index 90e1af8e..d8b32458 100644 --- a/Mastodon/Generated/Strings.swift +++ b/Mastodon/Generated/Strings.swift @@ -58,11 +58,11 @@ internal enum L10n { internal static let message = L10n.tr("Localizable", "Common.Alerts.PublishPostFailure.Message") /// Publish Failure internal static let title = L10n.tr("Localizable", "Common.Alerts.PublishPostFailure.Title") - internal enum AttchmentsMessage { + internal enum AttachmentsMessage { /// Cannot attach more than one video. - internal static let moreThanOneVideo = L10n.tr("Localizable", "Common.Alerts.PublishPostFailure.AttchmentsMessage.MoreThanOneVideo") + internal static let moreThanOneVideo = L10n.tr("Localizable", "Common.Alerts.PublishPostFailure.AttachmentsMessage.MoreThanOneVideo") /// Cannot attach a video to a post that already contains images. - internal static let videoAttachWithPhoto = L10n.tr("Localizable", "Common.Alerts.PublishPostFailure.AttchmentsMessage.VideoAttachWithPhoto") + internal static let videoAttachWithPhoto = L10n.tr("Localizable", "Common.Alerts.PublishPostFailure.AttachmentsMessage.VideoAttachWithPhoto") } } internal enum SavePhotoFailure { diff --git a/Mastodon/Helper/MastodonAuthenticationBox.swift b/Mastodon/Helper/MastodonAuthenticationBox.swift new file mode 100644 index 00000000..71ba50b5 --- /dev/null +++ b/Mastodon/Helper/MastodonAuthenticationBox.swift @@ -0,0 +1,17 @@ +// +// MastodonAuthenticationBox.swift +// Mastodon +// +// Created by MainasuK Cirno on 2021-7-20. +// + +import Foundation +import MastodonSDK +import CoreDataStack + +struct MastodonAuthenticationBox { + let domain: String + let userID: MastodonUser.ID + let appAuthorization: Mastodon.API.OAuth.Authorization + let userAuthorization: Mastodon.API.OAuth.Authorization +} diff --git a/Mastodon/Protocol/UserProvider/UserProviderFacade.swift b/Mastodon/Protocol/UserProvider/UserProviderFacade.swift index c9546696..bf634b07 100644 --- a/Mastodon/Protocol/UserProvider/UserProviderFacade.swift +++ b/Mastodon/Protocol/UserProvider/UserProviderFacade.swift @@ -49,7 +49,7 @@ extension UserProviderFacade { private static func _toggleUserFollowRelationship( context: AppContext, - activeMastodonAuthenticationBox: AuthenticationService.MastodonAuthenticationBox, + activeMastodonAuthenticationBox: MastodonAuthenticationBox, mastodonUser: AnyPublisher ) -> AnyPublisher, Error> { mastodonUser @@ -111,7 +111,7 @@ extension UserProviderFacade { private static func _toggleUserBlockRelationship( context: AppContext, - activeMastodonAuthenticationBox: AuthenticationService.MastodonAuthenticationBox, + activeMastodonAuthenticationBox: MastodonAuthenticationBox, mastodonUser: AnyPublisher ) -> AnyPublisher, Error> { mastodonUser @@ -174,7 +174,7 @@ extension UserProviderFacade { private static func _toggleUserMuteRelationship( context: AppContext, - activeMastodonAuthenticationBox: AuthenticationService.MastodonAuthenticationBox, + activeMastodonAuthenticationBox: MastodonAuthenticationBox, mastodonUser: AnyPublisher ) -> AnyPublisher, Error> { mastodonUser diff --git a/Mastodon/Resources/ar.lproj/Localizable.strings b/Mastodon/Resources/ar.lproj/Localizable.strings index 1bf75432..e7369046 100644 --- a/Mastodon/Resources/ar.lproj/Localizable.strings +++ b/Mastodon/Resources/ar.lproj/Localizable.strings @@ -10,8 +10,8 @@ "Common.Alerts.DiscardPostContent.Title" = "Discard Draft"; "Common.Alerts.EditProfileFailure.Message" = "Cannot edit profile. Please try again."; "Common.Alerts.EditProfileFailure.Title" = "Edit Profile Error"; -"Common.Alerts.PublishPostFailure.AttchmentsMessage.MoreThanOneVideo" = "Cannot attach more than one video."; -"Common.Alerts.PublishPostFailure.AttchmentsMessage.VideoAttachWithPhoto" = "Cannot attach a video to a post that already contains images."; +"Common.Alerts.PublishPostFailure.AttachmentsMessage.MoreThanOneVideo" = "Cannot attach more than one video."; +"Common.Alerts.PublishPostFailure.AttachmentsMessage.VideoAttachWithPhoto" = "Cannot attach a video to a post that already contains images."; "Common.Alerts.PublishPostFailure.Message" = "Failed to publish the post. Please check your internet connection."; "Common.Alerts.PublishPostFailure.Title" = "Publish Failure"; diff --git a/Mastodon/Resources/en.lproj/Localizable.strings b/Mastodon/Resources/en.lproj/Localizable.strings index 1bf75432..e7369046 100644 --- a/Mastodon/Resources/en.lproj/Localizable.strings +++ b/Mastodon/Resources/en.lproj/Localizable.strings @@ -10,8 +10,8 @@ "Common.Alerts.DiscardPostContent.Title" = "Discard Draft"; "Common.Alerts.EditProfileFailure.Message" = "Cannot edit profile. Please try again."; "Common.Alerts.EditProfileFailure.Title" = "Edit Profile Error"; -"Common.Alerts.PublishPostFailure.AttchmentsMessage.MoreThanOneVideo" = "Cannot attach more than one video."; -"Common.Alerts.PublishPostFailure.AttchmentsMessage.VideoAttachWithPhoto" = "Cannot attach a video to a post that already contains images."; +"Common.Alerts.PublishPostFailure.AttachmentsMessage.MoreThanOneVideo" = "Cannot attach more than one video."; +"Common.Alerts.PublishPostFailure.AttachmentsMessage.VideoAttachWithPhoto" = "Cannot attach a video to a post that already contains images."; "Common.Alerts.PublishPostFailure.Message" = "Failed to publish the post. Please check your internet connection."; "Common.Alerts.PublishPostFailure.Title" = "Publish Failure"; diff --git a/Mastodon/Scene/Compose/ComposeViewModel.swift b/Mastodon/Scene/Compose/ComposeViewModel.swift index 69b9836a..2e059c1a 100644 --- a/Mastodon/Scene/Compose/ComposeViewModel.swift +++ b/Mastodon/Scene/Compose/ComposeViewModel.swift @@ -28,7 +28,7 @@ final class ComposeViewModel: NSObject { let isContentWarningComposing = CurrentValueSubject(false) let selectedStatusVisibility: CurrentValueSubject let activeAuthentication: CurrentValueSubject - let activeAuthenticationBox: CurrentValueSubject + let activeAuthenticationBox: CurrentValueSubject let traitCollectionDidChangePublisher = CurrentValueSubject(Void()) // use CurrentValueSubject to make initial event emit let repliedToCellFrame = CurrentValueSubject(.zero) let autoCompleteRetryLayoutTimes = CurrentValueSubject(0) @@ -202,13 +202,13 @@ final class ComposeViewModel: NSObject { } .assign(to: \.value, on: characterCount) .store(in: &disposeBag) + // bind compose bar button item UI state let isComposeContentEmpty = composeStatusAttribute.composeContent .map { ($0 ?? "").isEmpty } - let isComposeContentValid = composeStatusAttribute.composeContent - .map { composeContent -> Bool in - let composeContent = composeContent ?? "" - return composeContent.count <= ComposeViewModel.composeContentLimit + let isComposeContentValid = characterCount + .map { characterCount -> Bool in + return characterCount <= ComposeViewModel.composeContentLimit } let isMediaEmpty = attachmentServices .map { $0.isEmpty } @@ -224,10 +224,10 @@ final class ComposeViewModel: NSObject { } let isPublishBarButtonItemEnabledPrecondition1 = Publishers.CombineLatest4( - isComposeContentEmpty.eraseToAnyPublisher(), - isComposeContentValid.eraseToAnyPublisher(), - isMediaEmpty.eraseToAnyPublisher(), - isMediaUploadAllSuccess.eraseToAnyPublisher() + isComposeContentEmpty, + isComposeContentValid, + isMediaEmpty, + isMediaUploadAllSuccess ) .map { isComposeContentEmpty, isComposeContentValid, isMediaEmpty, isMediaUploadAllSuccess -> Bool in if isMediaEmpty { @@ -239,10 +239,10 @@ final class ComposeViewModel: NSObject { .eraseToAnyPublisher() let isPublishBarButtonItemEnabledPrecondition2 = Publishers.CombineLatest4( - isComposeContentEmpty.eraseToAnyPublisher(), - isComposeContentValid.eraseToAnyPublisher(), - isPollComposing.eraseToAnyPublisher(), - isPollAttributeAllValid.eraseToAnyPublisher() + isComposeContentEmpty, + isComposeContentValid, + isPollComposing, + isPollAttributeAllValid ) .map { isComposeContentEmpty, isComposeContentValid, isPollComposing, isPollAttributeAllValid -> Bool in if isPollComposing { @@ -390,9 +390,9 @@ extension ComposeViewModel { var failureReason: String? { switch self { case .videoAttachWithPhoto: - return L10n.Common.Alerts.PublishPostFailure.AttchmentsMessage.videoAttachWithPhoto + return L10n.Common.Alerts.PublishPostFailure.AttachmentsMessage.videoAttachWithPhoto case .moreThanOneVideo: - return L10n.Common.Alerts.PublishPostFailure.AttchmentsMessage.moreThanOneVideo + return L10n.Common.Alerts.PublishPostFailure.AttachmentsMessage.moreThanOneVideo } } } diff --git a/Mastodon/Scene/Compose/TableViewCell/ComposeStatusAttachmentTableViewCell.swift b/Mastodon/Scene/Compose/TableViewCell/ComposeStatusAttachmentTableViewCell.swift index f8d2bcf3..623ec717 100644 --- a/Mastodon/Scene/Compose/TableViewCell/ComposeStatusAttachmentTableViewCell.swift +++ b/Mastodon/Scene/Compose/TableViewCell/ComposeStatusAttachmentTableViewCell.swift @@ -116,9 +116,11 @@ extension ComposeStatusAttachmentTableViewCell { } else { guard let uploadState = uploadState else { return } switch uploadState { - case is MastodonAttachmentService.UploadState.Finish, - is MastodonAttachmentService.UploadState.Fail: + case is MastodonAttachmentService.UploadState.Finish: cell.attachmentContainerView.activityIndicatorView.stopAnimating() + case is MastodonAttachmentService.UploadState.Fail: + cell.attachmentContainerView.activityIndicatorView.stopAnimating() + // FIXME: not display cell.attachmentContainerView.emptyStateView.label.text = { if let file = attachmentService.file.value { switch file { diff --git a/Mastodon/Scene/Notification/NotificationViewModel.swift b/Mastodon/Scene/Notification/NotificationViewModel.swift index 4c3b975c..8102f770 100644 --- a/Mastodon/Scene/Notification/NotificationViewModel.swift +++ b/Mastodon/Scene/Notification/NotificationViewModel.swift @@ -26,7 +26,7 @@ final class NotificationViewModel: NSObject { let selectedIndex = CurrentValueSubject(.EveryThing) let noMoreNotification = CurrentValueSubject(false) - let activeMastodonAuthenticationBox: CurrentValueSubject + let activeMastodonAuthenticationBox: CurrentValueSubject let fetchedResultsController: NSFetchedResultsController! let notificationPredicate = CurrentValueSubject(nil) let cellFrameCache = NSCache() diff --git a/Mastodon/Scene/Profile/Favorite/FavoriteViewModel.swift b/Mastodon/Scene/Profile/Favorite/FavoriteViewModel.swift index 589ffe19..6b4c1b8c 100644 --- a/Mastodon/Scene/Profile/Favorite/FavoriteViewModel.swift +++ b/Mastodon/Scene/Profile/Favorite/FavoriteViewModel.swift @@ -17,7 +17,7 @@ final class FavoriteViewModel { // input let context: AppContext - let activeMastodonAuthenticationBox: CurrentValueSubject + let activeMastodonAuthenticationBox: CurrentValueSubject let statusFetchedResultsController: StatusFetchedResultsController let cellFrameCache = NSCache() diff --git a/Mastodon/Scene/Profile/ProfileViewModel.swift b/Mastodon/Scene/Profile/ProfileViewModel.swift index 5e8874c2..05db1a88 100644 --- a/Mastodon/Scene/Profile/ProfileViewModel.swift +++ b/Mastodon/Scene/Profile/ProfileViewModel.swift @@ -115,7 +115,7 @@ class ProfileViewModel: NSObject { context.authenticationService.activeMastodonAuthenticationBox.eraseToAnyPublisher(), pendingRetryPublisher.eraseToAnyPublisher() ) - .compactMap { mastodonUserID, activeMastodonAuthenticationBox, _ -> (String, AuthenticationService.MastodonAuthenticationBox)? in + .compactMap { mastodonUserID, activeMastodonAuthenticationBox, _ -> (String, MastodonAuthenticationBox)? in guard let mastodonUserID = mastodonUserID, let activeMastodonAuthenticationBox = activeMastodonAuthenticationBox else { return nil } guard mastodonUserID != activeMastodonAuthenticationBox.userID else { return nil } return (mastodonUserID, activeMastodonAuthenticationBox) diff --git a/Mastodon/Scene/Report/ReportViewModel+Data.swift b/Mastodon/Scene/Report/ReportViewModel+Data.swift index df95cb00..178fc18a 100644 --- a/Mastodon/Scene/Report/ReportViewModel+Data.swift +++ b/Mastodon/Scene/Report/ReportViewModel+Data.swift @@ -17,7 +17,7 @@ extension ReportViewModel { func requestRecentStatus( domain: String, accountId: String, - authorizationBox: AuthenticationService.MastodonAuthenticationBox + authorizationBox: MastodonAuthenticationBox ) { context.apiService.userTimeline( domain: domain, diff --git a/Mastodon/Scene/Report/ReportViewModel.swift b/Mastodon/Scene/Report/ReportViewModel.swift index 8631963c..c8e59e8d 100644 --- a/Mastodon/Scene/Report/ReportViewModel.swift +++ b/Mastodon/Scene/Report/ReportViewModel.swift @@ -165,7 +165,7 @@ class ReportViewModel: NSObject { .store(in: &disposeBag) } - func bindForStep2(input: Input, domain: String, activeMastodonAuthenticationBox: AuthenticationService.MastodonAuthenticationBox) -> AnyPublisher<(Bool, Error?), Never> { + func bindForStep2(input: Input, domain: String, activeMastodonAuthenticationBox: MastodonAuthenticationBox) -> AnyPublisher<(Bool, Error?), Never> { let skip = input.step2Skip.map { [weak self] value -> Void in guard let self = self else { return value } self.reportQuery.comment = nil diff --git a/Mastodon/Scene/Search/Search/SearchViewModel.swift b/Mastodon/Scene/Search/Search/SearchViewModel.swift index 681fa0f5..4929ccca 100644 --- a/Mastodon/Scene/Search/Search/SearchViewModel.swift +++ b/Mastodon/Scene/Search/Search/SearchViewModel.swift @@ -42,7 +42,7 @@ final class SearchViewModel: NSObject { context.authenticationService.activeMastodonAuthenticationBox, viewDidAppeared ) - .compactMap { activeMastodonAuthenticationBox, _ -> AuthenticationService.MastodonAuthenticationBox? in + .compactMap { activeMastodonAuthenticationBox, _ -> MastodonAuthenticationBox? in return activeMastodonAuthenticationBox } .throttle(for: 1, scheduler: DispatchQueue.main, latest: false) @@ -72,7 +72,7 @@ final class SearchViewModel: NSObject { context.authenticationService.activeMastodonAuthenticationBox, viewDidAppeared ) - .compactMap { activeMastodonAuthenticationBox, _ -> AuthenticationService.MastodonAuthenticationBox? in + .compactMap { activeMastodonAuthenticationBox, _ -> MastodonAuthenticationBox? in return activeMastodonAuthenticationBox } .throttle(for: 1, scheduler: DispatchQueue.main, latest: false) diff --git a/Mastodon/Service/APIService/APIService+Block.swift b/Mastodon/Service/APIService/APIService+Block.swift index c28db9da..209ee361 100644 --- a/Mastodon/Service/APIService/APIService+Block.swift +++ b/Mastodon/Service/APIService/APIService+Block.swift @@ -16,7 +16,7 @@ extension APIService { func toggleBlock( for mastodonUser: MastodonUser, - activeMastodonAuthenticationBox: AuthenticationService.MastodonAuthenticationBox + activeMastodonAuthenticationBox: MastodonAuthenticationBox ) -> AnyPublisher, Error> { let impactFeedbackGenerator = UIImpactFeedbackGenerator(style: .light) let notificationFeedbackGenerator = UINotificationFeedbackGenerator() @@ -86,7 +86,7 @@ extension APIService { // update database local and return block query update type for remote request func blockUpdateLocal( mastodonUserObjectID: NSManagedObjectID, - mastodonAuthenticationBox: AuthenticationService.MastodonAuthenticationBox + mastodonAuthenticationBox: MastodonAuthenticationBox ) -> AnyPublisher<(Mastodon.API.Account.BlockQueryType, MastodonUser.ID), Error> { let domain = mastodonAuthenticationBox.domain let requestMastodonUserID = mastodonAuthenticationBox.userID @@ -132,7 +132,7 @@ extension APIService { func blockUpdateRemote( blockQueryType: Mastodon.API.Account.BlockQueryType, mastodonUserID: MastodonUser.ID, - mastodonAuthenticationBox: AuthenticationService.MastodonAuthenticationBox + mastodonAuthenticationBox: MastodonAuthenticationBox ) -> AnyPublisher, Error> { let domain = mastodonAuthenticationBox.domain let authorization = mastodonAuthenticationBox.userAuthorization diff --git a/Mastodon/Service/APIService/APIService+DomainBlock.swift b/Mastodon/Service/APIService/APIService+DomainBlock.swift index 887c3f07..222e1299 100644 --- a/Mastodon/Service/APIService/APIService+DomainBlock.swift +++ b/Mastodon/Service/APIService/APIService+DomainBlock.swift @@ -17,7 +17,7 @@ extension APIService { func getDomainblocks( domain: String, limit: Int = onceRequestDomainBlocksMaxCount, - authorizationBox: AuthenticationService.MastodonAuthenticationBox + authorizationBox: MastodonAuthenticationBox ) -> AnyPublisher, Error> { let authorization = authorizationBox.userAuthorization @@ -71,7 +71,7 @@ extension APIService { func blockDomain( user: MastodonUser, - authorizationBox: AuthenticationService.MastodonAuthenticationBox + authorizationBox: MastodonAuthenticationBox ) -> AnyPublisher, Error> { let authorization = authorizationBox.userAuthorization @@ -105,7 +105,7 @@ extension APIService { func unblockDomain( user: MastodonUser, - authorizationBox: AuthenticationService.MastodonAuthenticationBox + authorizationBox: MastodonAuthenticationBox ) -> AnyPublisher, Error> { let authorization = authorizationBox.userAuthorization diff --git a/Mastodon/Service/APIService/APIService+Favorite.swift b/Mastodon/Service/APIService/APIService+Favorite.swift index d3f1d81e..78a20d10 100644 --- a/Mastodon/Service/APIService/APIService+Favorite.swift +++ b/Mastodon/Service/APIService/APIService+Favorite.swift @@ -61,7 +61,7 @@ extension APIService { func favorite( statusID: Mastodon.Entity.Status.ID, favoriteKind: Mastodon.API.Favorites.FavoriteKind, - mastodonAuthenticationBox: AuthenticationService.MastodonAuthenticationBox + mastodonAuthenticationBox: MastodonAuthenticationBox ) -> AnyPublisher, Error> { let authorization = mastodonAuthenticationBox.userAuthorization let requestMastodonUserID = mastodonAuthenticationBox.userID @@ -139,7 +139,7 @@ extension APIService { func favoritedStatuses( limit: Int = onceRequestStatusMaxCount, maxID: String? = nil, - mastodonAuthenticationBox: AuthenticationService.MastodonAuthenticationBox + mastodonAuthenticationBox: MastodonAuthenticationBox ) -> AnyPublisher, Error> { let requestMastodonUserID = mastodonAuthenticationBox.userID diff --git a/Mastodon/Service/APIService/APIService+Filter.swift b/Mastodon/Service/APIService/APIService+Filter.swift index 5ecd1077..01f8087a 100644 --- a/Mastodon/Service/APIService/APIService+Filter.swift +++ b/Mastodon/Service/APIService/APIService+Filter.swift @@ -15,7 +15,7 @@ import MastodonSDK extension APIService { func filters( - mastodonAuthenticationBox: AuthenticationService.MastodonAuthenticationBox + mastodonAuthenticationBox: MastodonAuthenticationBox ) -> AnyPublisher, Error> { let authorization = mastodonAuthenticationBox.userAuthorization let domain = mastodonAuthenticationBox.domain diff --git a/Mastodon/Service/APIService/APIService+Follow.swift b/Mastodon/Service/APIService/APIService+Follow.swift index f527878f..ac2ccbea 100644 --- a/Mastodon/Service/APIService/APIService+Follow.swift +++ b/Mastodon/Service/APIService/APIService+Follow.swift @@ -24,7 +24,7 @@ extension APIService { /// - Returns: publisher for `Relationship` func toggleFollow( for mastodonUser: MastodonUser, - activeMastodonAuthenticationBox: AuthenticationService.MastodonAuthenticationBox + activeMastodonAuthenticationBox: MastodonAuthenticationBox ) -> AnyPublisher, Error> { let impactFeedbackGenerator = UIImpactFeedbackGenerator(style: .light) @@ -96,7 +96,7 @@ extension APIService { // update database local and return follow query update type for remote request func followUpdateLocal( mastodonUserObjectID: NSManagedObjectID, - mastodonAuthenticationBox: AuthenticationService.MastodonAuthenticationBox + mastodonAuthenticationBox: MastodonAuthenticationBox ) -> AnyPublisher<(Mastodon.API.Account.FollowQueryType, MastodonUser.ID), Error> { let domain = mastodonAuthenticationBox.domain let requestMastodonUserID = mastodonAuthenticationBox.userID @@ -156,7 +156,7 @@ extension APIService { func followUpdateRemote( followQueryType: Mastodon.API.Account.FollowQueryType, mastodonUserID: MastodonUser.ID, - mastodonAuthenticationBox: AuthenticationService.MastodonAuthenticationBox + mastodonAuthenticationBox: MastodonAuthenticationBox ) -> AnyPublisher, Error> { let domain = mastodonAuthenticationBox.domain let authorization = mastodonAuthenticationBox.userAuthorization diff --git a/Mastodon/Service/APIService/APIService+FollowRequest.swift b/Mastodon/Service/APIService/APIService+FollowRequest.swift index c40fcad5..0f5c3c25 100644 --- a/Mastodon/Service/APIService/APIService+FollowRequest.swift +++ b/Mastodon/Service/APIService/APIService+FollowRequest.swift @@ -17,7 +17,7 @@ import MastodonSDK extension APIService { func acceptFollowRequest( mastodonUserID: MastodonUser.ID, - mastodonAuthenticationBox: AuthenticationService.MastodonAuthenticationBox + mastodonAuthenticationBox: MastodonAuthenticationBox ) -> AnyPublisher, Error> { let domain = mastodonAuthenticationBox.domain let authorization = mastodonAuthenticationBox.userAuthorization @@ -61,7 +61,7 @@ extension APIService { func rejectFollowRequest( mastodonUserID: MastodonUser.ID, - mastodonAuthenticationBox: AuthenticationService.MastodonAuthenticationBox + mastodonAuthenticationBox: MastodonAuthenticationBox ) -> AnyPublisher, Error> { let domain = mastodonAuthenticationBox.domain let authorization = mastodonAuthenticationBox.userAuthorization diff --git a/Mastodon/Service/APIService/APIService+HashtagTimeline.swift b/Mastodon/Service/APIService/APIService+HashtagTimeline.swift index 69c2c748..241c7885 100644 --- a/Mastodon/Service/APIService/APIService+HashtagTimeline.swift +++ b/Mastodon/Service/APIService/APIService+HashtagTimeline.swift @@ -22,7 +22,7 @@ extension APIService { limit: Int = onceRequestStatusMaxCount, local: Bool? = nil, hashtag: String, - authorizationBox: AuthenticationService.MastodonAuthenticationBox + authorizationBox: MastodonAuthenticationBox ) -> AnyPublisher, Error> { let authorization = authorizationBox.userAuthorization let requestMastodonUserID = authorizationBox.userID diff --git a/Mastodon/Service/APIService/APIService+HomeTimeline.swift b/Mastodon/Service/APIService/APIService+HomeTimeline.swift index d4cbe69c..28f68274 100644 --- a/Mastodon/Service/APIService/APIService+HomeTimeline.swift +++ b/Mastodon/Service/APIService/APIService+HomeTimeline.swift @@ -21,7 +21,7 @@ extension APIService { maxID: Mastodon.Entity.Status.ID? = nil, limit: Int = onceRequestStatusMaxCount, local: Bool? = nil, - authorizationBox: AuthenticationService.MastodonAuthenticationBox + authorizationBox: MastodonAuthenticationBox ) -> AnyPublisher, Error> { let authorization = authorizationBox.userAuthorization let requestMastodonUserID = authorizationBox.userID diff --git a/Mastodon/Service/APIService/APIService+Media.swift b/Mastodon/Service/APIService/APIService+Media.swift index 7c1fd64d..d6b1d6c2 100644 --- a/Mastodon/Service/APIService/APIService+Media.swift +++ b/Mastodon/Service/APIService/APIService+Media.swift @@ -14,7 +14,7 @@ extension APIService { func uploadMedia( domain: String, query: Mastodon.API.Media.UploadMediaQuery, - mastodonAuthenticationBox: AuthenticationService.MastodonAuthenticationBox, + mastodonAuthenticationBox: MastodonAuthenticationBox, needsFallback: Bool ) -> AnyPublisher, Error> { if needsFallback { @@ -27,7 +27,7 @@ extension APIService { private func uploadMediaV1( domain: String, query: Mastodon.API.Media.UploadMediaQuery, - mastodonAuthenticationBox: AuthenticationService.MastodonAuthenticationBox + mastodonAuthenticationBox: MastodonAuthenticationBox ) -> AnyPublisher, Error> { let authorization = mastodonAuthenticationBox.userAuthorization @@ -42,7 +42,7 @@ extension APIService { private func uploadMediaV2( domain: String, query: Mastodon.API.Media.UploadMediaQuery, - mastodonAuthenticationBox: AuthenticationService.MastodonAuthenticationBox + mastodonAuthenticationBox: MastodonAuthenticationBox ) -> AnyPublisher, Error> { let authorization = mastodonAuthenticationBox.userAuthorization @@ -54,12 +54,16 @@ extension APIService { ) .eraseToAnyPublisher() } + +} + +extension APIService { func updateMedia( domain: String, attachmentID: Mastodon.Entity.Attachment.ID, query: Mastodon.API.Media.UpdateMediaQuery, - mastodonAuthenticationBox: AuthenticationService.MastodonAuthenticationBox + mastodonAuthenticationBox: MastodonAuthenticationBox ) -> AnyPublisher, Error> { let authorization = mastodonAuthenticationBox.userAuthorization diff --git a/Mastodon/Service/APIService/APIService+Mute.swift b/Mastodon/Service/APIService/APIService+Mute.swift index 9d992ab6..40f97acd 100644 --- a/Mastodon/Service/APIService/APIService+Mute.swift +++ b/Mastodon/Service/APIService/APIService+Mute.swift @@ -16,7 +16,7 @@ extension APIService { func toggleMute( for mastodonUser: MastodonUser, - activeMastodonAuthenticationBox: AuthenticationService.MastodonAuthenticationBox + activeMastodonAuthenticationBox: MastodonAuthenticationBox ) -> AnyPublisher, Error> { let impactFeedbackGenerator = UIImpactFeedbackGenerator(style: .light) let notificationFeedbackGenerator = UINotificationFeedbackGenerator() @@ -86,7 +86,7 @@ extension APIService { // update database local and return mute query update type for remote request func muteUpdateLocal( mastodonUserObjectID: NSManagedObjectID, - mastodonAuthenticationBox: AuthenticationService.MastodonAuthenticationBox + mastodonAuthenticationBox: MastodonAuthenticationBox ) -> AnyPublisher<(Mastodon.API.Account.MuteQueryType, MastodonUser.ID), Error> { let domain = mastodonAuthenticationBox.domain let requestMastodonUserID = mastodonAuthenticationBox.userID @@ -132,7 +132,7 @@ extension APIService { func muteUpdateRemote( muteQueryType: Mastodon.API.Account.MuteQueryType, mastodonUserID: MastodonUser.ID, - mastodonAuthenticationBox: AuthenticationService.MastodonAuthenticationBox + mastodonAuthenticationBox: MastodonAuthenticationBox ) -> AnyPublisher, Error> { let domain = mastodonAuthenticationBox.domain let authorization = mastodonAuthenticationBox.userAuthorization diff --git a/Mastodon/Service/APIService/APIService+Notification.swift b/Mastodon/Service/APIService/APIService+Notification.swift index dfd87bc1..9f7d3bb5 100644 --- a/Mastodon/Service/APIService/APIService+Notification.swift +++ b/Mastodon/Service/APIService/APIService+Notification.swift @@ -16,7 +16,7 @@ extension APIService { func allNotifications( domain: String, query: Mastodon.API.Notifications.Query, - mastodonAuthenticationBox: AuthenticationService.MastodonAuthenticationBox + mastodonAuthenticationBox: MastodonAuthenticationBox ) -> AnyPublisher, Error> { let authorization = mastodonAuthenticationBox.userAuthorization let userID = mastodonAuthenticationBox.userID @@ -75,7 +75,7 @@ extension APIService { func notification( notificationID: Mastodon.Entity.Notification.ID, - mastodonAuthenticationBox: AuthenticationService.MastodonAuthenticationBox + mastodonAuthenticationBox: MastodonAuthenticationBox ) -> AnyPublisher, Error> { let domain = mastodonAuthenticationBox.domain let authorization = mastodonAuthenticationBox.userAuthorization diff --git a/Mastodon/Service/APIService/APIService+Poll.swift b/Mastodon/Service/APIService/APIService+Poll.swift index 0b240466..ca091161 100644 --- a/Mastodon/Service/APIService/APIService+Poll.swift +++ b/Mastodon/Service/APIService/APIService+Poll.swift @@ -19,7 +19,7 @@ extension APIService { domain: String, pollID: Mastodon.Entity.Poll.ID, pollObjectID: NSManagedObjectID, - mastodonAuthenticationBox: AuthenticationService.MastodonAuthenticationBox + mastodonAuthenticationBox: MastodonAuthenticationBox ) -> AnyPublisher, Error> { let authorization = mastodonAuthenticationBox.userAuthorization let requestMastodonUserID = mastodonAuthenticationBox.userID @@ -143,7 +143,7 @@ extension APIService { pollID: Mastodon.Entity.Poll.ID, pollObjectID: NSManagedObjectID, choices: [Int], - mastodonAuthenticationBox: AuthenticationService.MastodonAuthenticationBox + mastodonAuthenticationBox: MastodonAuthenticationBox ) -> AnyPublisher, Error> { let authorization = mastodonAuthenticationBox.userAuthorization let requestMastodonUserID = mastodonAuthenticationBox.userID diff --git a/Mastodon/Service/APIService/APIService+Reblog.swift b/Mastodon/Service/APIService/APIService+Reblog.swift index adfac306..fd020614 100644 --- a/Mastodon/Service/APIService/APIService+Reblog.swift +++ b/Mastodon/Service/APIService/APIService+Reblog.swift @@ -62,7 +62,7 @@ extension APIService { func reblog( statusID: Mastodon.Entity.Status.ID, reblogKind: Mastodon.API.Reblog.ReblogKind, - mastodonAuthenticationBox: AuthenticationService.MastodonAuthenticationBox + mastodonAuthenticationBox: MastodonAuthenticationBox ) -> AnyPublisher, Error> { let domain = mastodonAuthenticationBox.domain let authorization = mastodonAuthenticationBox.userAuthorization diff --git a/Mastodon/Service/APIService/APIService+Recommend.swift b/Mastodon/Service/APIService/APIService+Recommend.swift index a3bcb3e3..458cb740 100644 --- a/Mastodon/Service/APIService/APIService+Recommend.swift +++ b/Mastodon/Service/APIService/APIService+Recommend.swift @@ -16,7 +16,7 @@ extension APIService { func suggestionAccount( domain: String, query: Mastodon.API.Suggestions.Query?, - mastodonAuthenticationBox: AuthenticationService.MastodonAuthenticationBox + mastodonAuthenticationBox: MastodonAuthenticationBox ) -> AnyPublisher, Error> { let authorization = mastodonAuthenticationBox.userAuthorization @@ -47,7 +47,7 @@ extension APIService { func suggestionAccountV2( domain: String, query: Mastodon.API.Suggestions.Query?, - mastodonAuthenticationBox: AuthenticationService.MastodonAuthenticationBox + mastodonAuthenticationBox: MastodonAuthenticationBox ) -> AnyPublisher, Error> { let authorization = mastodonAuthenticationBox.userAuthorization diff --git a/Mastodon/Service/APIService/APIService+Relationship.swift b/Mastodon/Service/APIService/APIService+Relationship.swift index b0ef2926..7efd2b39 100644 --- a/Mastodon/Service/APIService/APIService+Relationship.swift +++ b/Mastodon/Service/APIService/APIService+Relationship.swift @@ -17,7 +17,7 @@ extension APIService { func relationship( domain: String, accountIDs: [Mastodon.Entity.Account.ID], - authorizationBox: AuthenticationService.MastodonAuthenticationBox + authorizationBox: MastodonAuthenticationBox ) -> AnyPublisher, Error> { let authorization = authorizationBox.userAuthorization let requestMastodonUserID = authorizationBox.userID diff --git a/Mastodon/Service/APIService/APIService+Report.swift b/Mastodon/Service/APIService/APIService+Report.swift index 3c170c62..531c7218 100644 --- a/Mastodon/Service/APIService/APIService+Report.swift +++ b/Mastodon/Service/APIService/APIService+Report.swift @@ -14,7 +14,7 @@ extension APIService { func report( domain: String, query: Mastodon.API.Reports.FileReportQuery, - mastodonAuthenticationBox: AuthenticationService.MastodonAuthenticationBox + mastodonAuthenticationBox: MastodonAuthenticationBox ) -> AnyPublisher, Error> { let authorization = mastodonAuthenticationBox.userAuthorization diff --git a/Mastodon/Service/APIService/APIService+Search.swift b/Mastodon/Service/APIService/APIService+Search.swift index e1642fb5..4b636806 100644 --- a/Mastodon/Service/APIService/APIService+Search.swift +++ b/Mastodon/Service/APIService/APIService+Search.swift @@ -15,7 +15,7 @@ extension APIService { func search( domain: String, query: Mastodon.API.V2.Search.Query, - mastodonAuthenticationBox: AuthenticationService.MastodonAuthenticationBox + mastodonAuthenticationBox: MastodonAuthenticationBox ) -> AnyPublisher, Error> { let authorization = mastodonAuthenticationBox.userAuthorization let requestMastodonUserID = mastodonAuthenticationBox.userID diff --git a/Mastodon/Service/APIService/APIService+Status+Publish.swift b/Mastodon/Service/APIService/APIService+Status+Publish.swift new file mode 100644 index 00000000..45964602 --- /dev/null +++ b/Mastodon/Service/APIService/APIService+Status+Publish.swift @@ -0,0 +1,60 @@ +// +// APIService+Status+Publish.swift +// Mastodon +// +// Created by MainasuK Cirno on 2021-7-20. +// + +import Foundation +import Combine +import CoreData +import CoreDataStack +import CommonOSLog +import MastodonSDK + +extension APIService { + + func publishStatus( + domain: String, + query: Mastodon.API.Statuses.PublishStatusQuery, + mastodonAuthenticationBox: MastodonAuthenticationBox + ) -> AnyPublisher, Error> { + let authorization = mastodonAuthenticationBox.userAuthorization + + return Mastodon.API.Statuses.publishStatus( + session: session, + domain: domain, + query: query, + authorization: authorization + ) + .flatMap { response -> AnyPublisher, Error> in + #if APP_EXTENSION + return Just(response) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() + #else + return APIService.Persist.persistStatus( + managedObjectContext: self.backgroundManagedObjectContext, + domain: domain, + query: nil, + response: response.map { [$0] }, + persistType: .lookUp, + requestMastodonUserID: nil, + log: OSLog.api + ) + .setFailureType(to: Error.self) + .tryMap { result -> Mastodon.Response.Content in + switch result { + case .success: + return response + case .failure(let error): + throw error + } + } + .eraseToAnyPublisher() + #endif + } + .eraseToAnyPublisher() + } + +} diff --git a/Mastodon/Service/APIService/APIService+Status.swift b/Mastodon/Service/APIService/APIService+Status.swift index 01bc667e..7f82406f 100644 --- a/Mastodon/Service/APIService/APIService+Status.swift +++ b/Mastodon/Service/APIService/APIService+Status.swift @@ -14,48 +14,11 @@ import DateToolsSwift import MastodonSDK extension APIService { - - func publishStatus( - domain: String, - query: Mastodon.API.Statuses.PublishStatusQuery, - mastodonAuthenticationBox: AuthenticationService.MastodonAuthenticationBox - ) -> AnyPublisher, Error> { - let authorization = mastodonAuthenticationBox.userAuthorization - - return Mastodon.API.Statuses.publishStatus( - session: session, - domain: domain, - query: query, - authorization: authorization - ) - .flatMap { response -> AnyPublisher, Error> in - return APIService.Persist.persistStatus( - managedObjectContext: self.backgroundManagedObjectContext, - domain: domain, - query: nil, - response: response.map { [$0] }, - persistType: .lookUp, - requestMastodonUserID: nil, - log: OSLog.api - ) - .setFailureType(to: Error.self) - .tryMap { result -> Mastodon.Response.Content in - switch result { - case .success: - return response - case .failure(let error): - throw error - } - } - .eraseToAnyPublisher() - } - .eraseToAnyPublisher() - } func status( domain: String, statusID: Mastodon.Entity.Status.ID, - authorizationBox: AuthenticationService.MastodonAuthenticationBox + authorizationBox: MastodonAuthenticationBox ) -> AnyPublisher, Error> { let authorization = authorizationBox.userAuthorization return Mastodon.API.Statuses.status( @@ -91,7 +54,7 @@ extension APIService { func deleteStatus( domain: String, statusID: Mastodon.Entity.Status.ID, - authorizationBox: AuthenticationService.MastodonAuthenticationBox + authorizationBox: MastodonAuthenticationBox ) -> AnyPublisher, Error> { let authorization = authorizationBox.userAuthorization let query = Mastodon.API.Statuses.DeleteStatusQuery(id: statusID) diff --git a/Mastodon/Service/APIService/APIService+Subscriptions.swift b/Mastodon/Service/APIService/APIService+Subscriptions.swift index ceaff45f..e9df2bc5 100644 --- a/Mastodon/Service/APIService/APIService+Subscriptions.swift +++ b/Mastodon/Service/APIService/APIService+Subscriptions.swift @@ -17,7 +17,7 @@ extension APIService { func createSubscription( subscriptionObjectID: NSManagedObjectID, query: Mastodon.API.Subscriptions.CreateSubscriptionQuery, - mastodonAuthenticationBox: AuthenticationService.MastodonAuthenticationBox + mastodonAuthenticationBox: MastodonAuthenticationBox ) -> AnyPublisher, Error> { let authorization = mastodonAuthenticationBox.userAuthorization let domain = mastodonAuthenticationBox.domain @@ -50,7 +50,7 @@ extension APIService { } func cancelSubscription( - mastodonAuthenticationBox: AuthenticationService.MastodonAuthenticationBox + mastodonAuthenticationBox: MastodonAuthenticationBox ) -> AnyPublisher, Error> { let authorization = mastodonAuthenticationBox.userAuthorization let domain = mastodonAuthenticationBox.domain diff --git a/Mastodon/Service/APIService/APIService+Thread.swift b/Mastodon/Service/APIService/APIService+Thread.swift index 2633518c..3bebdffe 100644 --- a/Mastodon/Service/APIService/APIService+Thread.swift +++ b/Mastodon/Service/APIService/APIService+Thread.swift @@ -17,7 +17,7 @@ extension APIService { func statusContext( domain: String, statusID: Mastodon.Entity.Status.ID, - mastodonAuthenticationBox: AuthenticationService.MastodonAuthenticationBox + mastodonAuthenticationBox: MastodonAuthenticationBox ) -> AnyPublisher, Error> { let authorization = mastodonAuthenticationBox.userAuthorization guard domain == mastodonAuthenticationBox.domain else { diff --git a/Mastodon/Service/APIService/APIService+UserTimeline.swift b/Mastodon/Service/APIService/APIService+UserTimeline.swift index 0e1e223b..7a449d37 100644 --- a/Mastodon/Service/APIService/APIService+UserTimeline.swift +++ b/Mastodon/Service/APIService/APIService+UserTimeline.swift @@ -23,7 +23,7 @@ extension APIService { excludeReplies: Bool? = nil, excludeReblogs: Bool? = nil, onlyMedia: Bool? = nil, - authorizationBox: AuthenticationService.MastodonAuthenticationBox + authorizationBox: MastodonAuthenticationBox ) -> AnyPublisher, Error> { let authorization = authorizationBox.userAuthorization let requestMastodonUserID = authorizationBox.userID diff --git a/Mastodon/Service/APIService/APIService.swift b/Mastodon/Service/APIService/APIService.swift index 1316f2cf..9d1468ce 100644 --- a/Mastodon/Service/APIService/APIService.swift +++ b/Mastodon/Service/APIService/APIService.swift @@ -21,7 +21,6 @@ final class APIService { // internal let session: URLSession - // input let backgroundManagedObjectContext: NSManagedObjectContext diff --git a/Mastodon/Service/AuthenticationService.swift b/Mastodon/Service/AuthenticationService.swift index a0bbca57..f6ece044 100644 --- a/Mastodon/Service/AuthenticationService.swift +++ b/Mastodon/Service/AuthenticationService.swift @@ -24,9 +24,9 @@ final class AuthenticationService: NSObject { // output let mastodonAuthentications = CurrentValueSubject<[MastodonAuthentication], Never>([]) - let mastodonAuthenticationBoxes = CurrentValueSubject<[AuthenticationService.MastodonAuthenticationBox], Never>([]) + let mastodonAuthenticationBoxes = CurrentValueSubject<[MastodonAuthenticationBox], Never>([]) let activeMastodonAuthentication = CurrentValueSubject(nil) - let activeMastodonAuthenticationBox = CurrentValueSubject(nil) + let activeMastodonAuthenticationBox = CurrentValueSubject(nil) init( managedObjectContext: NSManagedObjectContext, @@ -61,11 +61,11 @@ final class AuthenticationService: NSObject { .store(in: &disposeBag) mastodonAuthentications - .map { authentications -> [AuthenticationService.MastodonAuthenticationBox] in + .map { authentications -> [MastodonAuthenticationBox] in return authentications .sorted(by: { $0.activedAt > $1.activedAt }) - .compactMap { authentication -> AuthenticationService.MastodonAuthenticationBox? in - return AuthenticationService.MastodonAuthenticationBox( + .compactMap { authentication -> MastodonAuthenticationBox? in + return MastodonAuthenticationBox( domain: authentication.domain, userID: authentication.userID, appAuthorization: Mastodon.API.OAuth.Authorization(accessToken: authentication.appAccessToken), @@ -91,15 +91,6 @@ final class AuthenticationService: NSObject { } -extension AuthenticationService { - struct MastodonAuthenticationBox { - let domain: String - let userID: MastodonUser.ID - let appAuthorization: Mastodon.API.OAuth.Authorization - let userAuthorization: Mastodon.API.OAuth.Authorization - } -} - extension AuthenticationService { func activeMastodonUser(domain: String, userID: MastodonUser.ID) -> AnyPublisher, Never> { @@ -133,7 +124,7 @@ extension AuthenticationService { guard let mastodonAuthentication = try? managedObjectContext.fetch(request).first else { return } - _mastodonAuthenticationBox = AuthenticationService.MastodonAuthenticationBox( + _mastodonAuthenticationBox = MastodonAuthenticationBox( domain: mastodonAuthentication.domain, userID: mastodonAuthentication.userID, appAuthorization: Mastodon.API.OAuth.Authorization(accessToken: mastodonAuthentication.appAccessToken), diff --git a/Mastodon/Service/MastodonAttachmentService/MastodonAttachmentService+UploadState.swift b/Mastodon/Service/MastodonAttachmentService/MastodonAttachmentService+UploadState.swift index 7976156f..8474ac4d 100644 --- a/Mastodon/Service/MastodonAttachmentService/MastodonAttachmentService+UploadState.swift +++ b/Mastodon/Service/MastodonAttachmentService/MastodonAttachmentService+UploadState.swift @@ -83,7 +83,7 @@ extension MastodonAttachmentService.UploadState { { self.needsFallback = true stateMachine.enter(Uploading.self) - os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: upload attachment fallback to V1", ((#file as NSString).lastPathComponent), #line, #function, error.localizedDescription) + os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: upload attachment fallback to V1", ((#file as NSString).lastPathComponent), #line, #function) } else { os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: upload attachment fail: %s", ((#file as NSString).lastPathComponent), #line, #function, error.localizedDescription) service.error.send(error) diff --git a/Mastodon/Service/MastodonAttachmentService/MastodonAttachmentService.swift b/Mastodon/Service/MastodonAttachmentService/MastodonAttachmentService.swift index f2841b26..2b08b0db 100644 --- a/Mastodon/Service/MastodonAttachmentService/MastodonAttachmentService.swift +++ b/Mastodon/Service/MastodonAttachmentService/MastodonAttachmentService.swift @@ -27,7 +27,7 @@ final class MastodonAttachmentService { // input let context: AppContext - var authenticationBox: AuthenticationService.MastodonAuthenticationBox? + var authenticationBox: MastodonAuthenticationBox? let file = CurrentValueSubject(nil) let description = CurrentValueSubject(nil) @@ -52,7 +52,7 @@ final class MastodonAttachmentService { init( context: AppContext, pickerResult: PHPickerResult, - initialAuthenticationBox: AuthenticationService.MastodonAuthenticationBox? + initialAuthenticationBox: MastodonAuthenticationBox? ) { self.context = context self.authenticationBox = initialAuthenticationBox @@ -90,7 +90,7 @@ final class MastodonAttachmentService { init( context: AppContext, image: UIImage, - initialAuthenticationBox: AuthenticationService.MastodonAuthenticationBox? + initialAuthenticationBox: MastodonAuthenticationBox? ) { self.context = context self.authenticationBox = initialAuthenticationBox @@ -105,7 +105,7 @@ final class MastodonAttachmentService { init( context: AppContext, documentURL: URL, - initialAuthenticationBox: AuthenticationService.MastodonAuthenticationBox? + initialAuthenticationBox: MastodonAuthenticationBox? ) { self.context = context self.authenticationBox = initialAuthenticationBox @@ -191,7 +191,7 @@ extension MastodonAttachmentService { extension MastodonAttachmentService { // FIXME: needs reset state for multiple account posting support - func uploading(mastodonAuthenticationBox: AuthenticationService.MastodonAuthenticationBox) -> Bool { + func uploading(mastodonAuthenticationBox: MastodonAuthenticationBox) -> Bool { authenticationBox = mastodonAuthenticationBox return uploadStateMachine.enter(UploadState.self) } diff --git a/Mastodon/Service/NotificationService.swift b/Mastodon/Service/NotificationService.swift index 76e9b522..ffe4c991 100644 --- a/Mastodon/Service/NotificationService.swift +++ b/Mastodon/Service/NotificationService.swift @@ -82,7 +82,7 @@ extension NotificationService { extension NotificationService { func dequeueNotificationViewModel( - mastodonAuthenticationBox: AuthenticationService.MastodonAuthenticationBox + mastodonAuthenticationBox: MastodonAuthenticationBox ) -> NotificationViewModel? { var _notificationSubscription: NotificationViewModel? workingQueue.sync { @@ -130,7 +130,7 @@ extension NotificationService { // cancel subscription if sign-out let accessToken = mastodonPushNotification.accessToken - let mastodonAuthenticationBox = AuthenticationService.MastodonAuthenticationBox( + let mastodonAuthenticationBox = MastodonAuthenticationBox( domain: domain, userID: userID, appAuthorization: .init(accessToken: accessToken), @@ -178,7 +178,7 @@ extension NotificationService.NotificationViewModel { func createSubscribeQuery( deviceToken: Data, queryData: Mastodon.API.Subscriptions.QueryData, - mastodonAuthenticationBox: AuthenticationService.MastodonAuthenticationBox + mastodonAuthenticationBox: MastodonAuthenticationBox ) -> Mastodon.API.Subscriptions.CreateSubscriptionQuery { let deviceToken = [UInt8](deviceToken).toHexString() diff --git a/Mastodon/Service/SettingService.swift b/Mastodon/Service/SettingService.swift index 7da8c368..37246765 100644 --- a/Mastodon/Service/SettingService.swift +++ b/Mastodon/Service/SettingService.swift @@ -41,7 +41,7 @@ final class SettingService { // create setting (if non-exist) for authenticated users authenticationService.mastodonAuthenticationBoxes - .compactMap { [weak self] mastodonAuthenticationBoxes -> AnyPublisher<[AuthenticationService.MastodonAuthenticationBox], Never>? in + .compactMap { [weak self] mastodonAuthenticationBoxes -> AnyPublisher<[MastodonAuthenticationBox], Never>? in guard let self = self else { return nil } guard let authenticationService = self.authenticationService else { return nil } guard let activeMastodonAuthenticationBox = mastodonAuthenticationBoxes.first else { return nil } diff --git a/Mastodon/Service/StatusPrefetchingService.swift b/Mastodon/Service/StatusPrefetchingService.swift index 34b1d7a0..e22ba69f 100644 --- a/Mastodon/Service/StatusPrefetchingService.swift +++ b/Mastodon/Service/StatusPrefetchingService.swift @@ -104,7 +104,7 @@ extension StatusPrefetchingService { statusObjectID: NSManagedObjectID, statusID: Mastodon.Entity.Status.ID, replyToStatusID: Mastodon.Entity.Status.ID, - authorizationBox: AuthenticationService.MastodonAuthenticationBox + authorizationBox: MastodonAuthenticationBox ) { workingQueue.async { [weak self] in guard let self = self, let apiService = self.apiService else { return } diff --git a/MastodonSDK/Sources/MastodonExtension/CGImage.swift b/MastodonSDK/Sources/MastodonExtension/CGImage.swift new file mode 100644 index 00000000..41f2de0b --- /dev/null +++ b/MastodonSDK/Sources/MastodonExtension/CGImage.swift @@ -0,0 +1,42 @@ +// +// CGImage.swift +// Mastodon +// +// Created by MainasuK Cirno on 2021-3-31. +// + +import CoreImage + +extension CGImage { + // Reference + // https://www.itu.int/dms_pubrec/itu-r/rec/bt/R-REC-BT.709-6-201506-I!!PDF-E.pdf + // Luma Y = 0.2126R + 0.7152G + 0.0722B + public var brightness: CGFloat? { + let context = CIContext() // default with metal accelerate + let ciImage = CIImage(cgImage: self) + let rec709Image = context.createCGImage( + ciImage, + from: ciImage.extent, + format: .RGBA8, + colorSpace: CGColorSpace(name: CGColorSpace.itur_709) // BT.709 a.k.a Rec.709 + ) + guard let image = rec709Image, + image.bitsPerPixel == 32, + let data = rec709Image?.dataProvider?.data, + let pointer = CFDataGetBytePtr(data) else { return nil } + + let length = CFDataGetLength(data) + guard length > 0 else { return nil } + + var luma: CGFloat = 0.0 + for i in stride(from: 0, to: length, by: 4) { + let r = pointer[i] + let g = pointer[i + 1] + let b = pointer[i + 2] + let Y = 0.2126 * CGFloat(r) + 0.7152 * CGFloat(g) + 0.0722 * CGFloat(b) + luma += Y + } + luma /= CGFloat(width * height) + return luma + } +} diff --git a/MastodonSDK/Sources/MastodonExtension/UIImage.swift b/MastodonSDK/Sources/MastodonExtension/UIImage.swift index 79896fda..178d289d 100644 --- a/MastodonSDK/Sources/MastodonExtension/UIImage.swift +++ b/MastodonSDK/Sources/MastodonExtension/UIImage.swift @@ -1,10 +1,12 @@ // // UIImage.swift -// +// Mastodon // -// Created by MainasuK Cirno on 2021-7-16. +// Created by sxiaojian on 2021/3/8. // +import CoreImage +import CoreImage.CIFilterBuiltins import UIKit extension UIImage { @@ -17,3 +19,74 @@ extension UIImage { } } } + +// refs: https://www.hackingwithswift.com/example-code/media/how-to-read-the-average-color-of-a-uiimage-using-ciareaaverage +extension UIImage { + @available(iOS 14.0, *) + public var dominantColor: UIColor? { + guard let inputImage = CIImage(image: self) else { return nil } + + let filter = CIFilter.areaAverage() + filter.inputImage = inputImage + filter.extent = inputImage.extent + guard let outputImage = filter.outputImage else { return nil } + + var bitmap = [UInt8](repeating: 0, count: 4) + let context = CIContext(options: [.workingColorSpace: kCFNull]) + context.render(outputImage, toBitmap: &bitmap, rowBytes: 4, bounds: CGRect(x: 0, y: 0, width: 1, height: 1), format: .RGBA8, colorSpace: nil) + + return UIColor(red: CGFloat(bitmap[0]) / 255, green: CGFloat(bitmap[1]) / 255, blue: CGFloat(bitmap[2]) / 255, alpha: CGFloat(bitmap[3]) / 255) + } +} + +extension UIImage { + public var domainLumaCoefficientsStyle: UIUserInterfaceStyle? { + guard let brightness = cgImage?.brightness else { return nil } + return brightness > 100 ? .light : .dark // 0 ~ 255 + } +} + +extension UIImage { + public func blur(radius: CGFloat) -> UIImage? { + guard let inputImage = CIImage(image: self) else { return nil } + let blurFilter = CIFilter.gaussianBlur() + blurFilter.inputImage = inputImage + blurFilter.radius = Float(radius) + guard let outputImage = blurFilter.outputImage else { return nil } + guard let cgImage = CIContext().createCGImage(outputImage, from: outputImage.extent) else { return nil } + let image = UIImage(cgImage: cgImage, scale: scale, orientation: imageOrientation) + return image + } +} + +extension UIImage { + public func withRoundedCorners(radius: CGFloat? = nil) -> UIImage? { + let maxRadius = min(size.width, size.height) / 2 + let cornerRadius: CGFloat = { + guard let radius = radius, radius > 0 else { return maxRadius } + return min(radius, maxRadius) + }() + + let render = UIGraphicsImageRenderer(size: size) + return render.image { (_: UIGraphicsImageRendererContext) in + let rect = CGRect(origin: .zero, size: size) + UIBezierPath(roundedRect: rect, cornerRadius: cornerRadius).addClip() + draw(in: rect) + } + } +} + +extension UIImage { + public static func adaptiveUserInterfaceStyleImage(lightImage: UIImage, darkImage: UIImage) -> UIImage { + let imageAsset = UIImageAsset() + imageAsset.register(lightImage, with: UITraitCollection(traitsFrom: [ + UITraitCollection(displayScale: 1.0), + UITraitCollection(userInterfaceStyle: .light) + ])) + imageAsset.register(darkImage, with: UITraitCollection(traitsFrom: [ + UITraitCollection(displayScale: 1.0), + UITraitCollection(userInterfaceStyle: .dark) + ])) + return imageAsset.image(with: UITraitCollection.current) + } +} diff --git a/MastodonSDK/Sources/MastodonUI/Vendor/ItemProviderLoader.swift b/MastodonSDK/Sources/MastodonUI/Vendor/ItemProviderLoader.swift index 2524fcb1..6662f90e 100644 --- a/MastodonSDK/Sources/MastodonUI/Vendor/ItemProviderLoader.swift +++ b/MastodonSDK/Sources/MastodonUI/Vendor/ItemProviderLoader.swift @@ -41,11 +41,17 @@ extension ItemProviderLoader { guard let source = CGImageSourceCreateWithURL(url as CFURL, sourceOptions) else { return } + + #if APP_EXTENSION + let maxPixelSize: Int = 4096 // not limit but may upload fail + #else + let maxPixelSize: Int = 1536 // fit 120MB RAM limit + #endif let downsampleOptions = [ kCGImageSourceCreateThumbnailFromImageAlways: true, kCGImageSourceCreateThumbnailWithTransform: true, - kCGImageSourceThumbnailMaxPixelSize: 4096, + kCGImageSourceThumbnailMaxPixelSize: maxPixelSize, ] as CFDictionary guard let cgImage = CGImageSourceCreateThumbnailAtIndex(source, 0, downsampleOptions) else { diff --git a/ShareActionExtension/Scene/ShareViewController.swift b/ShareActionExtension/Scene/ShareViewController.swift index 3fba5adb..a3cef66d 100644 --- a/ShareActionExtension/Scene/ShareViewController.swift +++ b/ShareActionExtension/Scene/ShareViewController.swift @@ -34,8 +34,7 @@ class ShareViewController: UIViewController { private(set) lazy var cancelBarButtonItem = UIBarButtonItem(title: L10n.Common.Controls.Actions.cancel, style: .plain, target: self, action: #selector(ShareViewController.cancelBarButtonItemPressed(_:))) private(set) lazy var publishBarButtonItem: UIBarButtonItem = { let barButtonItem = UIBarButtonItem(customView: publishButton) - barButtonItem.target = self - barButtonItem.action = #selector(ShareViewController.publishBarButtonItemPressed(_:)) + publishButton.addTarget(self, action: #selector(ShareViewController.publishBarButtonItemPressed(_:)), for: .touchUpInside) return barButtonItem }() @@ -173,6 +172,12 @@ extension ShareViewController { } } .store(in: &disposeBag) + + // bind valid + viewModel.isValid + .receive(on: DispatchQueue.main) + .assign(to: \.isEnabled, on: publishButton) + .store(in: &disposeBag) } override func viewDidAppear(_ animated: Bool) { @@ -180,6 +185,8 @@ extension ShareViewController { viewModel.viewDidAppear.value = true viewModel.inputItems.value = extensionContext?.inputItems.compactMap { $0 as? NSExtensionItem } ?? [] + + viewModel.composeViewModel.viewDidAppear = true } override func viewSafeAreaInsetsDidChange() { @@ -204,6 +211,42 @@ extension ShareViewController { @objc private func publishBarButtonItemPressed(_ sender: UIBarButtonItem) { logger.debug("\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public)") + + viewModel.isPublishing.value = true + + viewModel.publish() + .delay(for: 2, scheduler: DispatchQueue.main) + .receive(on: DispatchQueue.main) + .sink { [weak self] completion in + guard let self = self else { return } + self.viewModel.isPublishing.value = false + + switch completion { + case .failure: + let alertController = UIAlertController( + title: L10n.Common.Alerts.PublishPostFailure.title, + message: L10n.Common.Alerts.PublishPostFailure.message, + preferredStyle: .actionSheet // can not use alert in extension + ) + let okAction = UIAlertAction( + title: L10n.Common.Controls.Actions.ok, + style: .cancel, + handler: nil + ) + alertController.addAction(okAction) + self.present(alertController, animated: true, completion: nil) + case .finished: + self.publishButton.setTitle(L10n.Common.Controls.Actions.done, for: .normal) + self.publishButton.isUserInteractionEnabled = false + DispatchQueue.main.asyncAfter(deadline: .now() + 1) { [weak self] in + guard let self = self else { return } + self.extensionContext?.completeRequest(returningItems: nil, completionHandler: nil) + } + } + } receiveValue: { response in + // do nothing + } + .store(in: &disposeBag) } } diff --git a/ShareActionExtension/Scene/ShareViewModel.swift b/ShareActionExtension/Scene/ShareViewModel.swift index 777e5be5..060181ae 100644 --- a/ShareActionExtension/Scene/ShareViewModel.swift +++ b/ShareActionExtension/Scene/ShareViewModel.swift @@ -10,6 +10,7 @@ import Foundation import Combine import CoreData import CoreDataStack +import MastodonSDK import MastodonUI import SwiftUI import UniformTypeIdentifiers @@ -33,6 +34,7 @@ final class ShareViewModel { // output let authentication = CurrentValueSubject?, Never>(nil) let isFetchAuthentication = CurrentValueSubject(true) + let isPublishing = CurrentValueSubject(false) let isBusy = CurrentValueSubject(true) let isValid = CurrentValueSubject(false) let composeViewModel = ComposeViewModel() @@ -59,11 +61,13 @@ final class ShareViewModel { } .store(in: &disposeBag) + // bind authentication loading state authentication .map { result in result == nil } .assign(to: \.value, on: isFetchAuthentication) .store(in: &disposeBag) + // bind user locked state authentication .compactMap { result -> Bool? in guard let result = result else { return nil } @@ -80,15 +84,105 @@ final class ShareViewModel { .assign(to: \.value, on: selectedStatusVisibility) .store(in: &disposeBag) - isFetchAuthentication + // bind author + authentication .receive(on: DispatchQueue.main) - .assign(to: \.value, on: isBusy) + .sink { [weak self] result in + guard let self = self else { return } + guard let result = result else { return } + switch result { + case .success(let authentication): + self.composeViewModel.avatarImageURL = authentication.user.avatarImageURL() + self.composeViewModel.authorName = authentication.user.displayNameWithFallback + self.composeViewModel.authorUsername = "@" + authentication.user.username + case .failure: + self.composeViewModel.avatarImageURL = nil + self.composeViewModel.authorName = " " + self.composeViewModel.authorUsername = " " + } + } .store(in: &disposeBag) + // bind authentication to compose view model + authentication + .map { result -> MastodonAuthentication? in + guard let result = result else { return nil } + switch result { + case .success(let authentication): + return authentication + case .failure: + return nil + } + } + .assign(to: &composeViewModel.$authentication) + + // bind isBusy + Publishers.CombineLatest( + isFetchAuthentication, + isPublishing + ) + .receive(on: DispatchQueue.main) + .map { $0 || $1 } + .assign(to: \.value, on: isBusy) + .store(in: &disposeBag) + + // pass initial i18n string composeViewModel.statusPlaceholder = L10n.Scene.Compose.contentInputPlaceholder composeViewModel.contentWarningPlaceholder = L10n.Scene.Compose.ContentWarning.placeholder composeViewModel.toolbarHeight = ComposeToolbarView.toolbarHeight - + + // bind compose bar button item UI state + let isComposeContentEmpty = composeViewModel.$statusContent + .map { $0.isEmpty } + let isComposeContentValid = composeViewModel.$characterCount + .map { characterCount -> Bool in + return characterCount <= ShareViewModel.composeContentLimit + } + let isMediaEmpty = composeViewModel.$attachmentViewModels + .map { $0.isEmpty } + let isMediaUploadAllSuccess = composeViewModel.$attachmentViewModels + .map { viewModels in + viewModels.allSatisfy { $0.uploadStateMachineSubject.value is StatusAttachmentViewModel.UploadState.Finish } + } + + let isPublishBarButtonItemEnabledPrecondition1 = Publishers.CombineLatest4( + isComposeContentEmpty, + isComposeContentValid, + isMediaEmpty, + isMediaUploadAllSuccess + ) + .map { isComposeContentEmpty, isComposeContentValid, isMediaEmpty, isMediaUploadAllSuccess -> Bool in + if isMediaEmpty { + return isComposeContentValid && !isComposeContentEmpty + } else { + return isComposeContentValid && isMediaUploadAllSuccess + } + } + .eraseToAnyPublisher() + + let isPublishBarButtonItemEnabledPrecondition2 = Publishers.CombineLatest( + isComposeContentEmpty, + isComposeContentValid + ) + .map { isComposeContentEmpty, isComposeContentValid -> Bool in + return isComposeContentValid && !isComposeContentEmpty + } + .eraseToAnyPublisher() + + Publishers.CombineLatest( + isPublishBarButtonItemEnabledPrecondition1, + isPublishBarButtonItemEnabledPrecondition2 + ) + .map { $0 && $1 } + .assign(to: \.value, on: isValid) + .store(in: &disposeBag) + + // bind counter + composeViewModel.$characterCount + .assign(to: \.value, on: characterCount) + .store(in: &disposeBag) + + // setup theme setupBackgroundColor(theme: ThemeService.shared.currentTheme.value) ThemeService.shared.currentTheme .receive(on: DispatchQueue.main) @@ -97,10 +191,6 @@ final class ShareViewModel { self.setupBackgroundColor(theme: theme) } .store(in: &disposeBag) - - composeViewModel.$characterCount - .assign(to: \.value, on: characterCount) - .store(in: &disposeBag) } private func setupBackgroundColor(theme: Theme) { @@ -184,3 +274,76 @@ extension ShareViewModel { } } } + +extension ShareViewModel { + func publish() -> AnyPublisher, Error> { + guard let authentication = composeViewModel.authentication else { + return Fail(error: APIService.APIError.implicit(.authenticationMissing)).eraseToAnyPublisher() + } + let mastodonAuthenticationBox = MastodonAuthenticationBox( + domain: authentication.domain, + userID: authentication.userID, + appAuthorization: Mastodon.API.OAuth.Authorization(accessToken: authentication.appAccessToken), + userAuthorization: Mastodon.API.OAuth.Authorization(accessToken: authentication.userAccessToken) + ) + + let domain = authentication.domain + let attachmentViewModels = composeViewModel.attachmentViewModels + let mediaIDs = attachmentViewModels.compactMap { viewModel in + viewModel.attachment.value?.id + } + let sensitive: Bool = composeViewModel.isContentWarningComposing + let spoilerText: String? = { + let text = composeViewModel.contentWarningContent + guard !text.isEmpty else { return nil } + return text + }() + let visibility = selectedStatusVisibility.value.visibility + + let updateMediaQuerySubscriptions: [AnyPublisher, Error>] = { + var subscriptions: [AnyPublisher, Error>] = [] + for attachmentViewModel in attachmentViewModels { + guard let attachmentID = attachmentViewModel.attachment.value?.id else { continue } + let description = attachmentViewModel.descriptionContent.trimmingCharacters(in: .whitespacesAndNewlines) + guard !description.isEmpty else { continue } + let query = Mastodon.API.Media.UpdateMediaQuery( + file: nil, + thumbnail: nil, + description: description, + focus: nil + ) + let subscription = APIService.shared.updateMedia( + domain: domain, + attachmentID: attachmentID, + query: query, + mastodonAuthenticationBox: mastodonAuthenticationBox + ) + subscriptions.append(subscription) + } + return subscriptions + }() + + let status = composeViewModel.statusContent + + return Publishers.MergeMany(updateMediaQuerySubscriptions) + .collect() + .flatMap { attachments -> AnyPublisher, Error> in + let query = Mastodon.API.Statuses.PublishStatusQuery( + status: status, + mediaIDs: mediaIDs.isEmpty ? nil : mediaIDs, + pollOptions: nil, + pollExpiresIn: nil, + inReplyToID: nil, + sensitive: sensitive, + spoilerText: spoilerText, + visibility: visibility + ) + return APIService.shared.publishStatus( + domain: domain, + query: query, + mastodonAuthenticationBox: mastodonAuthenticationBox + ) + } + .eraseToAnyPublisher() + } +} diff --git a/ShareActionExtension/Scene/View/ComposeView.swift b/ShareActionExtension/Scene/View/ComposeView.swift index 65b30913..f65e35b4 100644 --- a/ShareActionExtension/Scene/View/ComposeView.swift +++ b/ShareActionExtension/Scene/View/ComposeView.swift @@ -47,7 +47,8 @@ public struct ComposeView: View { placeholder: viewModel.statusPlaceholder, width: statusEditorViewWidth, attributedString: viewModel.statusContentAttributedString, - keyboardType: .twitter + keyboardType: .twitter, + viewDidAppear: $viewModel.viewDidAppear ) .frame(width: statusEditorViewWidth) .frame(minHeight: 100) @@ -55,11 +56,23 @@ public struct ComposeView: View { .listRow() // Attachments - ForEach(viewModel.attachmentViewModels) { viewModel in + ForEach(viewModel.attachmentViewModels) { attachmentViewModel in + let descriptionBinding = Binding { + return attachmentViewModel.descriptionContent + } set: { newValue in + attachmentViewModel.descriptionContent = newValue + } + StatusAttachmentView( - image: viewModel.thumbnailImage, + image: attachmentViewModel.thumbnailImage, + descriptionPlaceholder: attachmentViewModel.descriptionPlaceholder, + description: descriptionBinding, + errorPrompt: attachmentViewModel.errorPrompt, + errorPromptImage: attachmentViewModel.errorPromptImage, + isUploading: attachmentViewModel.isUploading, + progressViewTintColor: attachmentViewModel.progressViewTintColor, removeButtonAction: { - self.viewModel.removeAttachmentViewModel(viewModel) + self.viewModel.removeAttachmentViewModel(attachmentViewModel) } ) } @@ -73,7 +86,7 @@ public struct ComposeView: View { .listRow() } // end List .introspectTableView(customize: { tableView in - tableView.keyboardDismissMode = .onDrag + // tableView.keyboardDismissMode = .onDrag tableView.verticalScrollIndicatorInsets.bottom = viewModel.toolbarHeight }) .preference( diff --git a/ShareActionExtension/Scene/View/ComposeViewModel.swift b/ShareActionExtension/Scene/View/ComposeViewModel.swift index 06db193d..8d60481d 100644 --- a/ShareActionExtension/Scene/View/ComposeViewModel.swift +++ b/ShareActionExtension/Scene/View/ComposeViewModel.swift @@ -8,12 +8,16 @@ import Foundation import SwiftUI import Combine +import CoreDataStack class ComposeViewModel: ObservableObject { var disposeBag = Set() + @Published var authentication: MastodonAuthentication? + @Published var toolbarHeight: CGFloat = 0 + @Published var viewDidAppear = false @Published var avatarImageURL: URL? @Published var authorName: String = "" @@ -51,10 +55,38 @@ class ComposeViewModel: ObservableObject { } .assign(to: &$characterCount) + // setup attribute updater + $attachmentViewModels + .receive(on: DispatchQueue.main) + .debounce(for: 0.3, scheduler: DispatchQueue.main) + .sink { attachmentViewModels in + // drive upload state + // make image upload in the queue + for attachmentViewModel in attachmentViewModels { + // skip when prefix N task when task finish OR fail OR uploading + guard let currentState = attachmentViewModel.uploadStateMachine.currentState else { break } + if currentState is StatusAttachmentViewModel.UploadState.Fail { + continue + } + if currentState is StatusAttachmentViewModel.UploadState.Finish { + continue + } + if currentState is StatusAttachmentViewModel.UploadState.Uploading { + break + } + // trigger uploading one by one + if currentState is StatusAttachmentViewModel.UploadState.Initial { + attachmentViewModel.uploadStateMachine.enter(StatusAttachmentViewModel.UploadState.Uploading.self) + break + } + } + } + .store(in: &disposeBag) + #if DEBUG - avatarImageURL = URL(string: "https://upload.wikimedia.org/wikipedia/commons/2/2c/Rotating_earth_%28large%29.gif") - authorName = "Alice" - authorUsername = "alice" + // avatarImageURL = URL(string: "https://upload.wikimedia.org/wikipedia/commons/2/2c/Rotating_earth_%28large%29.gif") + // authorName = "Alice" + // authorUsername = "alice" #endif } @@ -64,11 +96,18 @@ extension ComposeViewModel { func setupAttachmentViewModels(_ viewModels: [StatusAttachmentViewModel]) { attachmentViewModels = viewModels for viewModel in viewModels { + // set delegate + viewModel.delegate = self + // set observed viewModel.objectWillChange.sink { [weak self] _ in guard let self = self else { return } self.objectWillChange.send() } .store(in: &viewModel.disposeBag) + // bind authentication + $authentication + .assign(to: \.value, on: viewModel.authentication) + .store(in: &viewModel.disposeBag) } } @@ -78,3 +117,13 @@ extension ComposeViewModel { } } } + +// MARK: - StatusAttachmentViewModelDelegate +extension ComposeViewModel: StatusAttachmentViewModelDelegate { + func statusAttachmentViewModel(_ viewModel: StatusAttachmentViewModel, uploadStateDidChange state: StatusAttachmentViewModel.UploadState?) { + // trigger event update + DispatchQueue.main.async { + self.attachmentViewModels = self.attachmentViewModels + } + } +} diff --git a/ShareActionExtension/Scene/View/StatusAttachmentView.swift b/ShareActionExtension/Scene/View/StatusAttachmentView.swift index 0f52afeb..4bc2ff9a 100644 --- a/ShareActionExtension/Scene/View/StatusAttachmentView.swift +++ b/ShareActionExtension/Scene/View/StatusAttachmentView.swift @@ -6,33 +6,74 @@ // import SwiftUI +import Introspect struct StatusAttachmentView: View { let image: UIImage? + let descriptionPlaceholder: String + @Binding var description: String + let errorPrompt: String? + let errorPromptImage: UIImage + let isUploading: Bool + let progressViewTintColor: UIColor + let removeButtonAction: () -> Void var body: some View { let image = image ?? UIImage.placeholder(color: .systemFill) - Color.clear - .aspectRatio(CGSize(width: 16, height: 9), contentMode: .fill) - .overlay( - Image(uiImage: image) - .resizable() - .aspectRatio(contentMode: .fill) - ) - .background(Color.gray) - .cornerRadius(4) - .badgeView( - Button(action: { - removeButtonAction() - }, label: { - Image(systemName: "minus.circle.fill") - .renderingMode(.original) - .font(.system(size: 22, weight: .bold, design: .default)) - }) - .buttonStyle(BorderlessButtonStyle()) - ) + ZStack(alignment: .bottom) { + if let errorPrompt = errorPrompt { + Color.clear + .aspectRatio(CGSize(width: 16, height: 9), contentMode: .fill) + .overlay( + VStack(alignment: .center) { + Image(uiImage: errorPromptImage) + Text(errorPrompt) + .lineLimit(2) + } + ) + .background(Color.gray) + } else { + Color.clear + .aspectRatio(CGSize(width: 16, height: 9), contentMode: .fill) + .overlay( + Image(uiImage: image) + .resizable() + .aspectRatio(contentMode: .fill) + ) + .background(Color.gray) + LinearGradient(gradient: Gradient(colors: [Color(white: 0, opacity: 0.69), Color.clear]), startPoint: .bottom, endPoint: .top) + .frame(maxHeight: 71) + TextField("", text: $description) + .placeholder(when: description.isEmpty) { + Text(descriptionPlaceholder).foregroundColor(Color(white: 1, opacity: 0.6)) + .lineLimit(1) + } + .foregroundColor(.white) + .font(.system(size: 15, weight: .regular, design: .default)) + .padding(EdgeInsets(top: 0, leading: 8, bottom: 7, trailing: 8)) + } + } + .cornerRadius(4) + .badgeView( + Button(action: { + removeButtonAction() + }, label: { + Image(systemName: "minus.circle.fill") + .renderingMode(.original) + .font(.system(size: 22, weight: .bold, design: .default)) + }) + .buttonStyle(BorderlessButtonStyle()) + ) + .overlay( + Group { + if isUploading { + ProgressView() + .progressViewStyle(CircularProgressViewStyle(tint: Color(progressViewTintColor))) + } + } + ) } } @@ -49,12 +90,32 @@ extension View { } } +/// ref: https://stackoverflow.com/a/57715771/3797903 +extension View { + func placeholder( + when shouldShow: Bool, + alignment: Alignment = .leading, + @ViewBuilder placeholder: () -> Content) -> some View { + + ZStack(alignment: alignment) { + placeholder().opacity(shouldShow ? 1 : 0) + self + } + } +} + struct StatusAttachmentView_Previews: PreviewProvider { static var previews: some View { ScrollView { StatusAttachmentView( image: UIImage(systemName: "photo"), + descriptionPlaceholder: "Describe photo", + description: .constant(""), + errorPrompt: nil, + errorPromptImage: StatusAttachmentViewModel.photoFillSplitImage, + isUploading: true, + progressViewTintColor: .systemFill, removeButtonAction: { // do nothing } diff --git a/ShareActionExtension/Scene/View/StatusAttachmentViewModel+UploadState.swift b/ShareActionExtension/Scene/View/StatusAttachmentViewModel+UploadState.swift new file mode 100644 index 00000000..cfd0a4de --- /dev/null +++ b/ShareActionExtension/Scene/View/StatusAttachmentViewModel+UploadState.swift @@ -0,0 +1,129 @@ +// +// StatusAttachmentViewModel+UploadState.swift +// ShareActionExtension +// +// Created by MainasuK Cirno on 2021-7-20. +// + +import os.log +import Foundation +import Combine +import GameplayKit +import MastodonSDK + +extension StatusAttachmentViewModel { + class UploadState: GKState { + weak var viewModel: StatusAttachmentViewModel? + + init(viewModel: StatusAttachmentViewModel) { + self.viewModel = viewModel + } + + override func didEnter(from previousState: GKState?) { + os_log("%{public}s[%{public}ld], %{public}s: enter %s, previous: %s", ((#file as NSString).lastPathComponent), #line, #function, self.debugDescription, previousState.debugDescription) + viewModel?.uploadStateMachineSubject.send(self) + } + } +} + +extension StatusAttachmentViewModel.UploadState { + + class Initial: StatusAttachmentViewModel.UploadState { + override func isValidNextState(_ stateClass: AnyClass) -> Bool { + guard viewModel?.authentication.value != nil else { return false } + if stateClass == Initial.self { + return true + } + + if viewModel?.file.value != nil { + return stateClass == Uploading.self + } else { + return stateClass == Fail.self + } + } + } + + class Uploading: StatusAttachmentViewModel.UploadState { + let logger = Logger(subsystem: "StatusAttachmentViewModel.UploadState.Uploading", category: "logic") + var needsFallback = false + + override func isValidNextState(_ stateClass: AnyClass) -> Bool { + return stateClass == Fail.self || stateClass == Finish.self || stateClass == Uploading.self + } + + override func didEnter(from previousState: GKState?) { + super.didEnter(from: previousState) + + guard let viewModel = viewModel, let stateMachine = stateMachine else { return } + guard let authentication = viewModel.authentication.value else { return } + guard let file = viewModel.file.value else { return } + + let description = viewModel.descriptionContent + let query = Mastodon.API.Media.UploadMediaQuery( + file: file, + thumbnail: nil, + description: description, + focus: nil + ) + + let mastodonAuthenticationBox = MastodonAuthenticationBox( + domain: authentication.domain, + userID: authentication.userID, + appAuthorization: Mastodon.API.OAuth.Authorization(accessToken: authentication.appAccessToken), + userAuthorization: Mastodon.API.OAuth.Authorization(accessToken: authentication.userAccessToken) + ) + + // and needs clone the `query` if needs retry + APIService.shared.uploadMedia( + domain: mastodonAuthenticationBox.domain, + query: query, + mastodonAuthenticationBox: mastodonAuthenticationBox, + needsFallback: needsFallback + ) + .receive(on: DispatchQueue.main) + .sink { [weak self] completion in + guard let self = self else { return } + switch completion { + case .failure(let error): + if let apiError = error as? Mastodon.API.Error, + apiError.httpResponseStatus == .notFound, + self.needsFallback == false + { + self.needsFallback = true + stateMachine.enter(Uploading.self) + self.logger.debug("\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): fallback to V1") + } else { + self.logger.debug("\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): fail: \(error.localizedDescription)") + viewModel.error = error + stateMachine.enter(Fail.self) + } + case .finished: + self.logger.debug("\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): upload attachment success") + break + } + } receiveValue: { [weak self] response in + guard let self = self else { return } + self.logger.debug("\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): upload attachment \(response.value.id) success, \(response.value.url ?? "")") + viewModel.attachment.value = response.value + stateMachine.enter(Finish.self) + } + .store(in: &viewModel.disposeBag) + } + + } + + class Fail: StatusAttachmentViewModel.UploadState { + override func isValidNextState(_ stateClass: AnyClass) -> Bool { + // allow discard publishing + return stateClass == Uploading.self || stateClass == Finish.self + } + } + + class Finish: StatusAttachmentViewModel.UploadState { + override func isValidNextState(_ stateClass: AnyClass) -> Bool { + return false + } + } + +} + diff --git a/ShareActionExtension/Scene/View/StatusAttachmentViewModel.swift b/ShareActionExtension/Scene/View/StatusAttachmentViewModel.swift index ba6a4d51..f0c1e644 100644 --- a/ShareActionExtension/Scene/View/StatusAttachmentViewModel.swift +++ b/ShareActionExtension/Scene/View/StatusAttachmentViewModel.swift @@ -9,16 +9,29 @@ import os.log import Foundation import SwiftUI import Combine +import CoreDataStack import MastodonSDK import MastodonUI import AVFoundation +import GameplayKit import MobileCoreServices import UniformTypeIdentifiers +protocol StatusAttachmentViewModelDelegate: AnyObject { + func statusAttachmentViewModel(_ viewModel: StatusAttachmentViewModel, uploadStateDidChange state: StatusAttachmentViewModel.UploadState?) +} + final class StatusAttachmentViewModel: ObservableObject, Identifiable { + static let photoFillSplitImage = Asset.Connectivity.photoFillSplit.image.withRenderingMode(.alwaysTemplate) + static let videoSplashImage: UIImage = { + let image = UIImage(systemName: "video.slash")!.withConfiguration(UIImage.SymbolConfiguration(pointSize: 64)) + return image + }() + let logger = Logger(subsystem: "StatusAttachmentViewModel", category: "logic") + weak var delegate: StatusAttachmentViewModelDelegate? var disposeBag = Set() let id = UUID() @@ -26,15 +39,36 @@ final class StatusAttachmentViewModel: ObservableObject, Identifiable { // input let file = CurrentValueSubject(nil) - @Published var description = "" + let authentication = CurrentValueSubject(nil) + @Published var descriptionContent = "" // output + let attachment = CurrentValueSubject(nil) @Published var thumbnailImage: UIImage? + @Published var descriptionPlaceholder = "" + @Published var isUploading = true + @Published var progressViewTintColor = UIColor.systemFill @Published var error: Error? + @Published var errorPrompt: String? + @Published var errorPromptImage: UIImage = StatusAttachmentViewModel.photoFillSplitImage + + private(set) lazy var uploadStateMachine: GKStateMachine = { + // exclude timeline middle fetcher state + let stateMachine = GKStateMachine(states: [ + UploadState.Initial(viewModel: self), + UploadState.Uploading(viewModel: self), + UploadState.Fail(viewModel: self), + UploadState.Finish(viewModel: self), + ]) + stateMachine.enter(UploadState.Initial.self) + return stateMachine + }() + lazy var uploadStateMachineSubject = CurrentValueSubject(nil) init(itemProvider: NSItemProvider) { self.itemProvider = itemProvider + // bind attachment from item provider Just(itemProvider) .receive(on: DispatchQueue.main) .flatMap { result -> AnyPublisher in @@ -51,18 +85,49 @@ final class StatusAttachmentViewModel: ObservableObject, Identifiable { switch completion { case .failure(let error): self.error = error -// self.uploadStateMachine.enter(UploadState.Fail.self) + self.uploadStateMachine.enter(UploadState.Fail.self) case .finished: break } } receiveValue: { [weak self] file in guard let self = self else { return } self.file.value = file -// self.uploadStateMachine.enter(UploadState.Initial.self) + self.uploadStateMachine.enter(UploadState.Initial.self) } .store(in: &disposeBag) + // bind progress view tint color + $thumbnailImage + .receive(on: DispatchQueue.main) + .map { image -> UIColor in + guard let image = image else { return .systemFill } + switch image.domainLumaCoefficientsStyle { + case .light: + return UIColor.black.withAlphaComponent(0.8) + default: + return UIColor.white.withAlphaComponent(0.8) + } + } + .assign(to: &$progressViewTintColor) + // bind description placeholder and error prompt image + file + .receive(on: DispatchQueue.main) + .sink { [weak self] file in + guard let self = self else { return } + guard let file = file else { return } + switch file { + case .jpeg, .png, .gif: + self.descriptionPlaceholder = L10n.Scene.Compose.Attachment.descriptionPhoto + self.errorPromptImage = StatusAttachmentViewModel.photoFillSplitImage + case .other: + self.descriptionPlaceholder = L10n.Scene.Compose.Attachment.descriptionVideo + self.errorPromptImage = StatusAttachmentViewModel.videoSplashImage + } + } + .store(in: &disposeBag) + + // bind thumbnail image file .receive(on: DispatchQueue.main) .map { file -> UIImage? in @@ -92,6 +157,56 @@ final class StatusAttachmentViewModel: ObservableObject, Identifiable { } } .assign(to: &$thumbnailImage) + + // bind state and error + Publishers.CombineLatest( + uploadStateMachineSubject, + $error + ) + .sink { [weak self] state, error in + guard let self = self else { return } + // trigger delegate + self.delegate?.statusAttachmentViewModel(self, uploadStateDidChange: state) + + // set error prompt + if let error = error { + self.isUploading = false + self.errorPrompt = error.localizedDescription + } else { + guard let state = state else { return } + switch state { + case is UploadState.Finish: + self.isUploading = false + case is UploadState.Fail: + self.isUploading = false + // FIXME: not display + self.errorPrompt = { + guard let file = self.file.value else { + return L10n.Scene.Compose.Attachment.attachmentBroken(L10n.Scene.Compose.Attachment.photo) + } + switch file { + case .jpeg, .png, .gif: + return L10n.Scene.Compose.Attachment.attachmentBroken(L10n.Scene.Compose.Attachment.photo) + case .other: + return L10n.Scene.Compose.Attachment.attachmentBroken(L10n.Scene.Compose.Attachment.video) + } + }() + default: + break + } + } + } + .store(in: &disposeBag) + + // trigger delegate when authentication get new value + authentication + .receive(on: DispatchQueue.main) + .sink { [weak self] authentication in + guard let self = self else { return } + guard authentication != nil else { return } + self.delegate?.statusAttachmentViewModel(self, uploadStateDidChange: self.uploadStateMachineSubject.value) + } + .store(in: &disposeBag) } } diff --git a/ShareActionExtension/Scene/View/StatusAuthorView.swift b/ShareActionExtension/Scene/View/StatusAuthorView.swift index c729bb79..189a7adc 100644 --- a/ShareActionExtension/Scene/View/StatusAuthorView.swift +++ b/ShareActionExtension/Scene/View/StatusAuthorView.swift @@ -21,11 +21,12 @@ struct StatusAuthorView: View { HStack(spacing: 5) { AnimatedImage(imageURL: avatarImageURL) .frame(width: 42, height: 42) + .background(Color(UIColor.systemFill)) .cornerRadius(4) VStack(alignment: .leading) { Text(name) .font(.headline) - Text("@" + username) + Text(username) .font(.subheadline) .foregroundColor(.secondary) } diff --git a/ShareActionExtension/Scene/View/StatusEditorView.swift b/ShareActionExtension/Scene/View/StatusEditorView.swift index e6944883..79fedc00 100644 --- a/ShareActionExtension/Scene/View/StatusEditorView.swift +++ b/ShareActionExtension/Scene/View/StatusEditorView.swift @@ -16,19 +16,22 @@ public struct StatusEditorView: UIViewRepresentable { let width: CGFloat let attributedString: NSAttributedString let keyboardType: UIKeyboardType + @Binding var viewDidAppear: Bool public init( string: Binding, placeholder: String, width: CGFloat, attributedString: NSAttributedString, - keyboardType: UIKeyboardType + keyboardType: UIKeyboardType, + viewDidAppear: Binding ) { self._string = string self.placeholder = placeholder self.width = width self.attributedString = attributedString self.keyboardType = keyboardType + self._viewDidAppear = viewDidAppear } public func makeUIView(context: Context) -> UITextView { @@ -45,6 +48,7 @@ public struct StatusEditorView: UIViewRepresentable { let widthLayoutConstraint = textView.widthAnchor.constraint(equalToConstant: 100) widthLayoutConstraint.priority = .required - 1 context.coordinator.widthLayoutConstraint = widthLayoutConstraint + return textView } @@ -55,6 +59,12 @@ public struct StatusEditorView: UIViewRepresentable { // update layout context.coordinator.updateLayout(width: width) + + // set becomeFirstResponder + if viewDidAppear { + viewDidAppear = false + textView.becomeFirstResponder() + } } public func makeCoordinator() -> Coordinator { diff --git a/ShareActionExtension/Service/APIService.swift b/ShareActionExtension/Service/APIService.swift new file mode 100644 index 00000000..a8112167 --- /dev/null +++ b/ShareActionExtension/Service/APIService.swift @@ -0,0 +1,32 @@ +// +// APIService.swift +// ShareActionExtension +// +// Created by MainasuK Cirno on 2021-7-20. +// + +import os.log +import Foundation +import Combine +import CoreData +import CoreDataStack +import MastodonSDK + +// Replica APIService for share extension +final class APIService { + + var disposeBag = Set() + + static let shared = APIService() + + // internal + let session: URLSession + + // output + let error = PassthroughSubject() + + private init() { + self.session = URLSession(configuration: .default) + } + +} From 9c25bebaba398933d431b094d0570e21f71502d1 Mon Sep 17 00:00:00 2001 From: CMK Date: Tue, 20 Jul 2021 18:07:25 +0800 Subject: [PATCH 05/11] chore: set "See All" button hidden for hashtag recommend --- .../Scene/Search/Search/SearchViewController+Recommend.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mastodon/Scene/Search/Search/SearchViewController+Recommend.swift b/Mastodon/Scene/Search/Search/SearchViewController+Recommend.swift index e0b02e41..4365a63f 100644 --- a/Mastodon/Scene/Search/Search/SearchViewController+Recommend.swift +++ b/Mastodon/Scene/Search/Search/SearchViewController+Recommend.swift @@ -17,7 +17,7 @@ extension SearchViewController { let header = SearchRecommendCollectionHeader() header.titleLabel.text = L10n.Scene.Search.Recommend.HashTag.title header.descriptionLabel.text = L10n.Scene.Search.Recommend.HashTag.description - header.seeAllButton.addTarget(self, action: #selector(SearchViewController.hashtagSeeAllButtonPressed(_:)), for: .touchUpInside) + header.seeAllButton.isHidden = true stackView.addArrangedSubview(header) hashtagCollectionView.register(SearchRecommendTagsCollectionViewCell.self, forCellWithReuseIdentifier: String(describing: SearchRecommendTagsCollectionViewCell.self)) From 7b8500e1200797d4820f2cb804d9c18c1d651a00 Mon Sep 17 00:00:00 2001 From: CMK Date: Tue, 20 Jul 2021 19:24:24 +0800 Subject: [PATCH 06/11] feat: set theme color for share action extension --- Localization/app.json | 2 +- .../Onboarding/CategoryPickerSection.swift | 2 +- .../Section/Search/SearchResultSection.swift | 12 ---- .../Section/Status/PollSection.swift | 2 +- Mastodon/Generated/Assets.swift | 46 +++++++------ Mastodon/Generated/Strings.swift | 2 +- .../Contents.json | 38 ----------- .../Contents.json | 38 ----------- .../Contents.json | 38 ----------- .../Colors/{Background => Poll}/Contents.json | 0 .../Poll/disabled.colorset/Contents.json | 0 .../alert.yellow.colorset/Contents.json | 0 .../danger.border.colorset/Contents.json | 6 +- .../Contents.json | 6 +- .../toolbar.background.colorset/Contents.json | 38 ----------- .../Contents.json | 12 ++-- .../Contents.json | 38 +++++++++++ .../Background}/Contents.json | 0 .../Background/danger.colorset/Contents.json | 6 +- .../Contents.json | 6 +- .../Contents.json | 12 ++-- .../Contents.json | 12 ++-- .../system.background.colorset/Contents.json | 38 +++++++++++ .../Contents.json | 6 +- .../Contents.json | 38 +++++++++++ .../Contents.json | 38 +++++++++++ .../Contents.json | 38 +++++++++++ .../Compose/Contents.json | 0 .../background.colorset}/Contents.json | 6 +- .../toolbar.background.colorset/Contents.json | 38 +++++++++++ .../Assets.xcassets/_Deprecated/Contents.json | 9 +++ .../Resources/ar.lproj/Localizable.strings | 2 +- .../Resources/en.lproj/Localizable.strings | 2 +- .../View/AutoCompleteTopChevronView.swift | 27 ++++++-- ...seStatusAttachmentCollectionViewCell.swift | 4 +- .../Scene/Compose/ComposeViewController.swift | 7 +- ...ComposeStatusAttachmentTableViewCell.swift | 2 +- .../Compose/View/ComposeToolbarView.swift | 15 ++++- .../HomeTimelineNavigationBarTitleView.swift | 2 +- .../NotificationStatusTableViewCell.swift | 6 +- .../MastodonPickServerViewController.swift | 4 +- .../PickServerCategoriesCell.swift | 2 +- .../TableViewCell/PickServerCell.swift | 4 +- .../PickServerLoaderTableViewCell.swift | 4 +- .../TableViewCell/PickServerSearchCell.swift | 4 +- .../TableViewCell/PickServerTitleCell.swift | 2 +- .../View/PickServerCategoryView.swift | 2 +- .../View/PickServerEmptyStateView.swift | 2 +- .../MastodonRegisterViewController.swift | 14 ++-- .../MastodonServerRulesViewController.swift | 4 +- .../OnboardingViewControllerAppearance.swift | 4 +- .../Header/View/ProfileHeaderView.swift | 2 +- Mastodon/Scene/Profile/ProfileViewModel.swift | 4 +- ...ContainerView+MediaTypeIndicotorView.swift | 2 +- .../Share/View/Content/PollOptionView.swift | 3 +- .../ThreadReplyLoaderTableViewCell.swift | 3 +- .../SuggestionAccountViewController.swift | 11 +++- .../Service/ThemeService/MastodonTheme.swift | 1 + .../Service/ThemeService/SystemTheme.swift | 1 + Mastodon/Service/ThemeService/Theme.swift | 2 + .../Scene/ShareViewController.swift | 64 +++++++++++++++++-- .../Scene/ShareViewModel.swift | 6 ++ .../Scene/View/ComposeToolbarView.swift | 15 ++++- .../Scene/View/ComposeView.swift | 24 ++++--- .../Scene/View/ComposeViewModel.swift | 1 + .../Scene/View/StatusEditorView.swift | 1 + 66 files changed, 484 insertions(+), 296 deletions(-) delete mode 100644 Mastodon/Resources/Assets.xcassets/Colors/Background/system.grouped.background.colorset/Contents.json delete mode 100644 Mastodon/Resources/Assets.xcassets/Colors/Background/tertiary.system.background.colorset/Contents.json delete mode 100644 Mastodon/Resources/Assets.xcassets/Colors/Background/tertiary.system.grouped.background.colorset/Contents.json rename Mastodon/Resources/Assets.xcassets/Colors/{Background => Poll}/Contents.json (100%) rename Mastodon/Resources/Assets.xcassets/Colors/{Background => }/Poll/disabled.colorset/Contents.json (100%) rename Mastodon/Resources/Assets.xcassets/Colors/{Background => }/alert.yellow.colorset/Contents.json (100%) rename Mastodon/Resources/Assets.xcassets/Colors/{Background => }/danger.border.colorset/Contents.json (74%) rename Mastodon/Resources/Assets.xcassets/Colors/{Background => }/media.type.indicotor.colorset/Contents.json (74%) delete mode 100644 Mastodon/Resources/Assets.xcassets/Scene/Compose/toolbar.background.colorset/Contents.json rename Mastodon/Resources/Assets.xcassets/{Colors/Background/secondary.grouped.system.background.colorset => Theme/Mastodon/Background/compose.toolbar.background.colorset}/Contents.json (76%) create mode 100644 Mastodon/Resources/Assets.xcassets/Theme/system/Background/compose.toolbar.background.colorset/Contents.json rename Mastodon/Resources/Assets.xcassets/{Colors/Background/Poll => _Deprecated/Background}/Contents.json (100%) rename Mastodon/Resources/Assets.xcassets/{Colors => _Deprecated}/Background/danger.colorset/Contents.json (74%) rename Mastodon/Resources/Assets.xcassets/{Colors => _Deprecated}/Background/onboarding.background.colorset/Contents.json (74%) rename Mastodon/Resources/Assets.xcassets/{Colors/Background/secondary.system.background.colorset => _Deprecated/Background/secondary.grouped.system.background.colorset}/Contents.json (76%) rename Mastodon/Resources/Assets.xcassets/{Colors/Background/system.background.colorset => _Deprecated/Background/secondary.system.background.colorset}/Contents.json (76%) create mode 100644 Mastodon/Resources/Assets.xcassets/_Deprecated/Background/system.background.colorset/Contents.json rename Mastodon/Resources/Assets.xcassets/{Scene/Compose/background.colorset => _Deprecated/Background/system.elevated.background.colorset}/Contents.json (88%) create mode 100644 Mastodon/Resources/Assets.xcassets/_Deprecated/Background/system.grouped.background.colorset/Contents.json create mode 100644 Mastodon/Resources/Assets.xcassets/_Deprecated/Background/tertiary.system.background.colorset/Contents.json create mode 100644 Mastodon/Resources/Assets.xcassets/_Deprecated/Background/tertiary.system.grouped.background.colorset/Contents.json rename Mastodon/Resources/Assets.xcassets/{Scene => _Deprecated}/Compose/Contents.json (100%) rename Mastodon/Resources/Assets.xcassets/{Colors/Background/system.elevated.background.colorset => _Deprecated/Compose/background.colorset}/Contents.json (88%) create mode 100644 Mastodon/Resources/Assets.xcassets/_Deprecated/Compose/toolbar.background.colorset/Contents.json create mode 100644 Mastodon/Resources/Assets.xcassets/_Deprecated/Contents.json diff --git a/Localization/app.json b/Localization/app.json index f1024620..049094d4 100644 --- a/Localization/app.json +++ b/Localization/app.json @@ -524,7 +524,7 @@ }, "boring_zone": { "title": "The Boring Zone", - "account_settings": "Account settings", + "account_settings": "Account Settings", "terms": "Terms of Service", "privacy": "Privacy Policy" }, diff --git a/Mastodon/Diffiable/Section/Onboarding/CategoryPickerSection.swift b/Mastodon/Diffiable/Section/Onboarding/CategoryPickerSection.swift index 7ab93cc5..732813c0 100644 --- a/Mastodon/Diffiable/Section/Onboarding/CategoryPickerSection.swift +++ b/Mastodon/Diffiable/Section/Onboarding/CategoryPickerSection.swift @@ -34,7 +34,7 @@ extension CategoryPickerSection { cell.categoryView.titleLabel.textColor = .white } } else { - cell.categoryView.bgView.backgroundColor = Asset.Colors.Background.secondaryGroupedSystemBackground.color + cell.categoryView.bgView.backgroundColor = Asset.Theme.Mastodon.secondaryGroupedSystemBackground.color cell.categoryView.bgView.applyShadow(color: Asset.Colors.brandBlue.color, alpha: 0, x: 0, y: 0, blur: 0.0) if case .all = item { cell.categoryView.titleLabel.textColor = Asset.Colors.brandBlue.color diff --git a/Mastodon/Diffiable/Section/Search/SearchResultSection.swift b/Mastodon/Diffiable/Section/Search/SearchResultSection.swift index 5d26a682..dcc52e15 100644 --- a/Mastodon/Diffiable/Section/Search/SearchResultSection.swift +++ b/Mastodon/Diffiable/Section/Search/SearchResultSection.swift @@ -33,16 +33,6 @@ extension SearchResultSection { let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: SearchResultTableViewCell.self), for: indexPath) as! SearchResultTableViewCell cell.config(with: tag) return cell -// case .hashtagObjectID(let hashtagObjectID): -// let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: SearchingTableViewCell.self), for: indexPath) as! SearchingTableViewCell -// let tag = dependency.context.managedObjectContext.object(with: hashtagObjectID) as! Tag -// cell.config(with: tag) -// return cell -// case .accountObjectID(let accountObjectID): -// let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: SearchingTableViewCell.self), for: indexPath) as! SearchingTableViewCell -// let user = dependency.context.managedObjectContext.object(with: accountObjectID) as! MastodonUser -// cell.config(with: user) -// return cell case .status(let statusObjectID, let attribute): let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: StatusTableViewCell.self), for: indexPath) as! StatusTableViewCell if let status = try? dependency.context.managedObjectContext.existingObject(with: statusObjectID) as? Status { @@ -73,8 +63,6 @@ extension SearchResultSection { cell.loadMoreLabel.isHidden = true } return cell - default: - fatalError() } // end switch } // end UITableViewDiffableDataSource } // end func diff --git a/Mastodon/Diffiable/Section/Status/PollSection.swift b/Mastodon/Diffiable/Section/Status/PollSection.swift index 79ecd415..add2a79b 100644 --- a/Mastodon/Diffiable/Section/Status/PollSection.swift +++ b/Mastodon/Diffiable/Section/Status/PollSection.swift @@ -103,7 +103,7 @@ extension PollSection { cell.pollOptionView.optionPercentageLabel.isHidden = false cell.pollOptionView.optionPercentageLabel.text = String(Int(100 * percentage)) + "%" cell.pollOptionView.voteProgressStripView.isHidden = false - cell.pollOptionView.voteProgressStripView.tintColor = voted ? Asset.Colors.brandBlue.color : Asset.Colors.Background.Poll.disabled.color + cell.pollOptionView.voteProgressStripView.tintColor = voted ? Asset.Colors.brandBlue.color : Asset.Colors.Poll.disabled.color cell.pollOptionView.voteProgressStripView.setProgress(CGFloat(percentage), animated: animated) } } diff --git a/Mastodon/Generated/Assets.swift b/Mastodon/Generated/Assets.swift index 294a3c6a..0f405ef0 100644 --- a/Mastodon/Generated/Assets.swift +++ b/Mastodon/Generated/Assets.swift @@ -32,23 +32,6 @@ internal enum Asset { internal static let plusCircle = ImageAsset(name: "Circles/plus.circle") } internal enum Colors { - internal enum Background { - internal enum Poll { - internal static let disabled = ColorAsset(name: "Colors/Background/Poll/disabled") - } - internal static let alertYellow = ColorAsset(name: "Colors/Background/alert.yellow") - internal static let dangerBorder = ColorAsset(name: "Colors/Background/danger.border") - internal static let danger = ColorAsset(name: "Colors/Background/danger") - internal static let mediaTypeIndicotor = ColorAsset(name: "Colors/Background/media.type.indicotor") - internal static let onboardingBackground = ColorAsset(name: "Colors/Background/onboarding.background") - internal static let secondaryGroupedSystemBackground = ColorAsset(name: "Colors/Background/secondary.grouped.system.background") - internal static let secondarySystemBackground = ColorAsset(name: "Colors/Background/secondary.system.background") - internal static let systemBackground = ColorAsset(name: "Colors/Background/system.background") - internal static let systemElevatedBackground = ColorAsset(name: "Colors/Background/system.elevated.background") - internal static let systemGroupedBackground = ColorAsset(name: "Colors/Background/system.grouped.background") - internal static let tertiarySystemBackground = ColorAsset(name: "Colors/Background/tertiary.system.background") - internal static let tertiarySystemGroupedBackground = ColorAsset(name: "Colors/Background/tertiary.system.grouped.background") - } internal enum Border { internal static let composePoll = ColorAsset(name: "Colors/Border/compose.poll") internal static let notificationStatus = ColorAsset(name: "Colors/Border/notification.status") @@ -73,6 +56,9 @@ internal enum Asset { internal static let mention = ColorAsset(name: "Colors/Notification/mention") internal static let reblog = ColorAsset(name: "Colors/Notification/reblog") } + internal enum Poll { + internal static let disabled = ColorAsset(name: "Colors/Poll/disabled") + } internal enum Shadow { internal static let searchCard = ColorAsset(name: "Colors/Shadow/SearchCard") } @@ -87,12 +73,15 @@ internal enum Asset { internal static let invalid = ColorAsset(name: "Colors/TextField/invalid") internal static let valid = ColorAsset(name: "Colors/TextField/valid") } + internal static let alertYellow = ColorAsset(name: "Colors/alert.yellow") internal static let battleshipGrey = ColorAsset(name: "Colors/battleshipGrey") internal static let brandBlue = ColorAsset(name: "Colors/brand.blue") internal static let brandBlueDarken20 = ColorAsset(name: "Colors/brand.blue.darken.20") + internal static let dangerBorder = ColorAsset(name: "Colors/danger.border") internal static let danger = ColorAsset(name: "Colors/danger") internal static let disabled = ColorAsset(name: "Colors/disabled") internal static let inactive = ColorAsset(name: "Colors/inactive") + internal static let mediaTypeIndicotor = ColorAsset(name: "Colors/media.type.indicotor") internal static let successGreen = ColorAsset(name: "Colors/success.green") internal static let systemOrange = ColorAsset(name: "Colors/system.orange") } @@ -103,10 +92,6 @@ internal enum Asset { internal static let faceSmilingAdaptive = ImageAsset(name: "Human/face.smiling.adaptive") } internal enum Scene { - internal enum Compose { - internal static let background = ColorAsset(name: "Scene/Compose/background") - internal static let toolbarBackground = ColorAsset(name: "Scene/Compose/toolbar.background") - } internal enum Profile { internal enum Banner { internal static let bioEditBackgroundGray = ColorAsset(name: "Scene/Profile/Banner/bio.edit.background.gray") @@ -136,6 +121,7 @@ internal enum Asset { } internal enum Theme { internal enum Mastodon { + internal static let composeToolbarBackground = ColorAsset(name: "Theme/Mastodon/compose.toolbar.background") internal static let contentWarningOverlayBackground = ColorAsset(name: "Theme/Mastodon/content.warning.overlay.background") internal static let navigationBarBackground = ColorAsset(name: "Theme/Mastodon/navigation.bar.background") internal static let profileFieldCollectionViewBackground = ColorAsset(name: "Theme/Mastodon/profile.field.collection.view.background") @@ -153,6 +139,7 @@ internal enum Asset { internal static let tabBarItemInactiveIconColor = ColorAsset(name: "Theme/Mastodon/tab.bar.item.inactive.icon.color") } internal enum System { + internal static let composeToolbarBackground = ColorAsset(name: "Theme/system/compose.toolbar.background") internal static let contentWarningOverlayBackground = ColorAsset(name: "Theme/system/content.warning.overlay.background") internal static let navigationBarBackground = ColorAsset(name: "Theme/system/navigation.bar.background") internal static let profileFieldCollectionViewBackground = ColorAsset(name: "Theme/system/profile.field.collection.view.background") @@ -170,6 +157,23 @@ internal enum Asset { internal static let tabBarItemInactiveIconColor = ColorAsset(name: "Theme/system/tab.bar.item.inactive.icon.color") } } + internal enum Deprecated { + internal enum Background { + internal static let danger = ColorAsset(name: "_Deprecated/Background/danger") + internal static let onboardingBackground = ColorAsset(name: "_Deprecated/Background/onboarding.background") + internal static let secondaryGroupedSystemBackground = ColorAsset(name: "_Deprecated/Background/secondary.grouped.system.background") + internal static let secondarySystemBackground = ColorAsset(name: "_Deprecated/Background/secondary.system.background") + internal static let systemBackground = ColorAsset(name: "_Deprecated/Background/system.background") + internal static let systemElevatedBackground = ColorAsset(name: "_Deprecated/Background/system.elevated.background") + internal static let systemGroupedBackground = ColorAsset(name: "_Deprecated/Background/system.grouped.background") + internal static let tertiarySystemBackground = ColorAsset(name: "_Deprecated/Background/tertiary.system.background") + internal static let tertiarySystemGroupedBackground = ColorAsset(name: "_Deprecated/Background/tertiary.system.grouped.background") + } + internal enum Compose { + internal static let background = ColorAsset(name: "_Deprecated/Compose/background") + internal static let toolbarBackground = ColorAsset(name: "_Deprecated/Compose/toolbar.background") + } + } } // swiftlint:enable identifier_name line_length nesting type_body_length type_name diff --git a/Mastodon/Generated/Strings.swift b/Mastodon/Generated/Strings.swift index d8b32458..8d5443ab 100644 --- a/Mastodon/Generated/Strings.swift +++ b/Mastodon/Generated/Strings.swift @@ -950,7 +950,7 @@ internal enum L10n { internal static let trueBlackDarkMode = L10n.tr("Localizable", "Scene.Settings.Section.AppearanceSettings.TrueBlackDarkMode") } internal enum BoringZone { - /// Account settings + /// Account Settings internal static let accountSettings = L10n.tr("Localizable", "Scene.Settings.Section.BoringZone.AccountSettings") /// Privacy Policy internal static let privacy = L10n.tr("Localizable", "Scene.Settings.Section.BoringZone.Privacy") diff --git a/Mastodon/Resources/Assets.xcassets/Colors/Background/system.grouped.background.colorset/Contents.json b/Mastodon/Resources/Assets.xcassets/Colors/Background/system.grouped.background.colorset/Contents.json deleted file mode 100644 index 6ea6c820..00000000 --- a/Mastodon/Resources/Assets.xcassets/Colors/Background/system.grouped.background.colorset/Contents.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "colors" : [ - { - "color" : { - "color-space" : "srgb", - "components" : { - "alpha" : "1.000", - "blue" : "0xE8", - "green" : "0xE1", - "red" : "0xD9" - } - }, - "idiom" : "universal" - }, - { - "appearances" : [ - { - "appearance" : "luminosity", - "value" : "dark" - } - ], - "color" : { - "color-space" : "srgb", - "components" : { - "alpha" : "1.000", - "blue" : "34", - "green" : "27", - "red" : "25" - } - }, - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/Mastodon/Resources/Assets.xcassets/Colors/Background/tertiary.system.background.colorset/Contents.json b/Mastodon/Resources/Assets.xcassets/Colors/Background/tertiary.system.background.colorset/Contents.json deleted file mode 100644 index c19f25c8..00000000 --- a/Mastodon/Resources/Assets.xcassets/Colors/Background/tertiary.system.background.colorset/Contents.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "colors" : [ - { - "color" : { - "color-space" : "srgb", - "components" : { - "alpha" : "1.000", - "blue" : "0xFE", - "green" : "0xFF", - "red" : "0xFE" - } - }, - "idiom" : "universal" - }, - { - "appearances" : [ - { - "appearance" : "luminosity", - "value" : "dark" - } - ], - "color" : { - "color-space" : "srgb", - "components" : { - "alpha" : "1.000", - "blue" : "55", - "green" : "44", - "red" : "40" - } - }, - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/Mastodon/Resources/Assets.xcassets/Colors/Background/tertiary.system.grouped.background.colorset/Contents.json b/Mastodon/Resources/Assets.xcassets/Colors/Background/tertiary.system.grouped.background.colorset/Contents.json deleted file mode 100644 index 66083b13..00000000 --- a/Mastodon/Resources/Assets.xcassets/Colors/Background/tertiary.system.grouped.background.colorset/Contents.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "colors" : [ - { - "color" : { - "color-space" : "srgb", - "components" : { - "alpha" : "1.000", - "blue" : "0xE8", - "green" : "0xE1", - "red" : "0xD9" - } - }, - "idiom" : "universal" - }, - { - "appearances" : [ - { - "appearance" : "luminosity", - "value" : "dark" - } - ], - "color" : { - "color-space" : "srgb", - "components" : { - "alpha" : "1.000", - "blue" : "67", - "green" : "53", - "red" : "49" - } - }, - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/Mastodon/Resources/Assets.xcassets/Colors/Background/Contents.json b/Mastodon/Resources/Assets.xcassets/Colors/Poll/Contents.json similarity index 100% rename from Mastodon/Resources/Assets.xcassets/Colors/Background/Contents.json rename to Mastodon/Resources/Assets.xcassets/Colors/Poll/Contents.json diff --git a/Mastodon/Resources/Assets.xcassets/Colors/Background/Poll/disabled.colorset/Contents.json b/Mastodon/Resources/Assets.xcassets/Colors/Poll/disabled.colorset/Contents.json similarity index 100% rename from Mastodon/Resources/Assets.xcassets/Colors/Background/Poll/disabled.colorset/Contents.json rename to Mastodon/Resources/Assets.xcassets/Colors/Poll/disabled.colorset/Contents.json diff --git a/Mastodon/Resources/Assets.xcassets/Colors/Background/alert.yellow.colorset/Contents.json b/Mastodon/Resources/Assets.xcassets/Colors/alert.yellow.colorset/Contents.json similarity index 100% rename from Mastodon/Resources/Assets.xcassets/Colors/Background/alert.yellow.colorset/Contents.json rename to Mastodon/Resources/Assets.xcassets/Colors/alert.yellow.colorset/Contents.json diff --git a/Mastodon/Resources/Assets.xcassets/Colors/Background/danger.border.colorset/Contents.json b/Mastodon/Resources/Assets.xcassets/Colors/danger.border.colorset/Contents.json similarity index 74% rename from Mastodon/Resources/Assets.xcassets/Colors/Background/danger.border.colorset/Contents.json rename to Mastodon/Resources/Assets.xcassets/Colors/danger.border.colorset/Contents.json index bc9f94fc..144f8376 100644 --- a/Mastodon/Resources/Assets.xcassets/Colors/Background/danger.border.colorset/Contents.json +++ b/Mastodon/Resources/Assets.xcassets/Colors/danger.border.colorset/Contents.json @@ -5,9 +5,9 @@ "color-space" : "srgb", "components" : { "alpha" : "1.000", - "blue" : "66", - "green" : "46", - "red" : "163" + "blue" : "0.259", + "green" : "0.180", + "red" : "0.639" } }, "idiom" : "universal" diff --git a/Mastodon/Resources/Assets.xcassets/Colors/Background/media.type.indicotor.colorset/Contents.json b/Mastodon/Resources/Assets.xcassets/Colors/media.type.indicotor.colorset/Contents.json similarity index 74% rename from Mastodon/Resources/Assets.xcassets/Colors/Background/media.type.indicotor.colorset/Contents.json rename to Mastodon/Resources/Assets.xcassets/Colors/media.type.indicotor.colorset/Contents.json index e9c583c0..13d9ecff 100644 --- a/Mastodon/Resources/Assets.xcassets/Colors/Background/media.type.indicotor.colorset/Contents.json +++ b/Mastodon/Resources/Assets.xcassets/Colors/media.type.indicotor.colorset/Contents.json @@ -5,9 +5,9 @@ "color-space" : "srgb", "components" : { "alpha" : "0.600", - "blue" : "0", - "green" : "0", - "red" : "0" + "blue" : "0.000", + "green" : "0.000", + "red" : "0.000" } }, "idiom" : "universal" diff --git a/Mastodon/Resources/Assets.xcassets/Scene/Compose/toolbar.background.colorset/Contents.json b/Mastodon/Resources/Assets.xcassets/Scene/Compose/toolbar.background.colorset/Contents.json deleted file mode 100644 index 9fba7740..00000000 --- a/Mastodon/Resources/Assets.xcassets/Scene/Compose/toolbar.background.colorset/Contents.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "colors" : [ - { - "color" : { - "color-space" : "srgb", - "components" : { - "alpha" : "1.000", - "blue" : "222", - "green" : "216", - "red" : "214" - } - }, - "idiom" : "universal" - }, - { - "appearances" : [ - { - "appearance" : "luminosity", - "value" : "dark" - } - ], - "color" : { - "color-space" : "srgb", - "components" : { - "alpha" : "1.000", - "blue" : "32", - "green" : "32", - "red" : "32" - } - }, - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/Mastodon/Resources/Assets.xcassets/Colors/Background/secondary.grouped.system.background.colorset/Contents.json b/Mastodon/Resources/Assets.xcassets/Theme/Mastodon/Background/compose.toolbar.background.colorset/Contents.json similarity index 76% rename from Mastodon/Resources/Assets.xcassets/Colors/Background/secondary.grouped.system.background.colorset/Contents.json rename to Mastodon/Resources/Assets.xcassets/Theme/Mastodon/Background/compose.toolbar.background.colorset/Contents.json index acd80352..356b3551 100644 --- a/Mastodon/Resources/Assets.xcassets/Colors/Background/secondary.grouped.system.background.colorset/Contents.json +++ b/Mastodon/Resources/Assets.xcassets/Theme/Mastodon/Background/compose.toolbar.background.colorset/Contents.json @@ -5,9 +5,9 @@ "color-space" : "srgb", "components" : { "alpha" : "1.000", - "blue" : "254", - "green" : "255", - "red" : "254" + "blue" : "0.871", + "green" : "0.847", + "red" : "0.839" } }, "idiom" : "universal" @@ -23,9 +23,9 @@ "color-space" : "srgb", "components" : { "alpha" : "1.000", - "blue" : "67", - "green" : "53", - "red" : "49" + "blue" : "0.263", + "green" : "0.208", + "red" : "0.192" } }, "idiom" : "universal" diff --git a/Mastodon/Resources/Assets.xcassets/Theme/system/Background/compose.toolbar.background.colorset/Contents.json b/Mastodon/Resources/Assets.xcassets/Theme/system/Background/compose.toolbar.background.colorset/Contents.json new file mode 100644 index 00000000..da7b7606 --- /dev/null +++ b/Mastodon/Resources/Assets.xcassets/Theme/system/Background/compose.toolbar.background.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.871", + "green" : "0.847", + "red" : "0.839" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "0.920", + "blue" : "0.125", + "green" : "0.125", + "red" : "0.125" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Mastodon/Resources/Assets.xcassets/Colors/Background/Poll/Contents.json b/Mastodon/Resources/Assets.xcassets/_Deprecated/Background/Contents.json similarity index 100% rename from Mastodon/Resources/Assets.xcassets/Colors/Background/Poll/Contents.json rename to Mastodon/Resources/Assets.xcassets/_Deprecated/Background/Contents.json diff --git a/Mastodon/Resources/Assets.xcassets/Colors/Background/danger.colorset/Contents.json b/Mastodon/Resources/Assets.xcassets/_Deprecated/Background/danger.colorset/Contents.json similarity index 74% rename from Mastodon/Resources/Assets.xcassets/Colors/Background/danger.colorset/Contents.json rename to Mastodon/Resources/Assets.xcassets/_Deprecated/Background/danger.colorset/Contents.json index b77cb3c7..dabccc33 100644 --- a/Mastodon/Resources/Assets.xcassets/Colors/Background/danger.colorset/Contents.json +++ b/Mastodon/Resources/Assets.xcassets/_Deprecated/Background/danger.colorset/Contents.json @@ -5,9 +5,9 @@ "color-space" : "srgb", "components" : { "alpha" : "1.000", - "blue" : "90", - "green" : "64", - "red" : "223" + "blue" : "0.353", + "green" : "0.251", + "red" : "0.875" } }, "idiom" : "universal" diff --git a/Mastodon/Resources/Assets.xcassets/Colors/Background/onboarding.background.colorset/Contents.json b/Mastodon/Resources/Assets.xcassets/_Deprecated/Background/onboarding.background.colorset/Contents.json similarity index 74% rename from Mastodon/Resources/Assets.xcassets/Colors/Background/onboarding.background.colorset/Contents.json rename to Mastodon/Resources/Assets.xcassets/_Deprecated/Background/onboarding.background.colorset/Contents.json index 838e44e4..0e4687fb 100644 --- a/Mastodon/Resources/Assets.xcassets/Colors/Background/onboarding.background.colorset/Contents.json +++ b/Mastodon/Resources/Assets.xcassets/_Deprecated/Background/onboarding.background.colorset/Contents.json @@ -5,9 +5,9 @@ "color-space" : "srgb", "components" : { "alpha" : "1.000", - "blue" : "232", - "green" : "225", - "red" : "217" + "blue" : "0.910", + "green" : "0.882", + "red" : "0.851" } }, "idiom" : "universal" diff --git a/Mastodon/Resources/Assets.xcassets/Colors/Background/secondary.system.background.colorset/Contents.json b/Mastodon/Resources/Assets.xcassets/_Deprecated/Background/secondary.grouped.system.background.colorset/Contents.json similarity index 76% rename from Mastodon/Resources/Assets.xcassets/Colors/Background/secondary.system.background.colorset/Contents.json rename to Mastodon/Resources/Assets.xcassets/_Deprecated/Background/secondary.grouped.system.background.colorset/Contents.json index e4dc3161..ef6c7f7b 100644 --- a/Mastodon/Resources/Assets.xcassets/Colors/Background/secondary.system.background.colorset/Contents.json +++ b/Mastodon/Resources/Assets.xcassets/_Deprecated/Background/secondary.grouped.system.background.colorset/Contents.json @@ -5,9 +5,9 @@ "color-space" : "srgb", "components" : { "alpha" : "1.000", - "blue" : "232", - "green" : "225", - "red" : "217" + "blue" : "0.996", + "green" : "1.000", + "red" : "0.996" } }, "idiom" : "universal" @@ -23,9 +23,9 @@ "color-space" : "srgb", "components" : { "alpha" : "1.000", - "blue" : "34", - "green" : "27", - "red" : "25" + "blue" : "0.263", + "green" : "0.208", + "red" : "0.192" } }, "idiom" : "universal" diff --git a/Mastodon/Resources/Assets.xcassets/Colors/Background/system.background.colorset/Contents.json b/Mastodon/Resources/Assets.xcassets/_Deprecated/Background/secondary.system.background.colorset/Contents.json similarity index 76% rename from Mastodon/Resources/Assets.xcassets/Colors/Background/system.background.colorset/Contents.json rename to Mastodon/Resources/Assets.xcassets/_Deprecated/Background/secondary.system.background.colorset/Contents.json index f09411b7..c915c891 100644 --- a/Mastodon/Resources/Assets.xcassets/Colors/Background/system.background.colorset/Contents.json +++ b/Mastodon/Resources/Assets.xcassets/_Deprecated/Background/secondary.system.background.colorset/Contents.json @@ -5,9 +5,9 @@ "color-space" : "srgb", "components" : { "alpha" : "1.000", - "blue" : "254", - "green" : "255", - "red" : "254" + "blue" : "0.910", + "green" : "0.882", + "red" : "0.851" } }, "idiom" : "universal" @@ -23,9 +23,9 @@ "color-space" : "srgb", "components" : { "alpha" : "1.000", - "blue" : "0x37", - "green" : "0x2C", - "red" : "0x28" + "blue" : "0.133", + "green" : "0.106", + "red" : "0.098" } }, "idiom" : "universal" diff --git a/Mastodon/Resources/Assets.xcassets/_Deprecated/Background/system.background.colorset/Contents.json b/Mastodon/Resources/Assets.xcassets/_Deprecated/Background/system.background.colorset/Contents.json new file mode 100644 index 00000000..4572c240 --- /dev/null +++ b/Mastodon/Resources/Assets.xcassets/_Deprecated/Background/system.background.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.996", + "green" : "1.000", + "red" : "0.996" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.216", + "green" : "0.173", + "red" : "0.157" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Mastodon/Resources/Assets.xcassets/Scene/Compose/background.colorset/Contents.json b/Mastodon/Resources/Assets.xcassets/_Deprecated/Background/system.elevated.background.colorset/Contents.json similarity index 88% rename from Mastodon/Resources/Assets.xcassets/Scene/Compose/background.colorset/Contents.json rename to Mastodon/Resources/Assets.xcassets/_Deprecated/Background/system.elevated.background.colorset/Contents.json index dd6cbfd9..33b71ef9 100644 --- a/Mastodon/Resources/Assets.xcassets/Scene/Compose/background.colorset/Contents.json +++ b/Mastodon/Resources/Assets.xcassets/_Deprecated/Background/system.elevated.background.colorset/Contents.json @@ -23,9 +23,9 @@ "color-space" : "srgb", "components" : { "alpha" : "1.000", - "blue" : "55", - "green" : "44", - "red" : "40" + "blue" : "0.216", + "green" : "0.173", + "red" : "0.157" } }, "idiom" : "universal" diff --git a/Mastodon/Resources/Assets.xcassets/_Deprecated/Background/system.grouped.background.colorset/Contents.json b/Mastodon/Resources/Assets.xcassets/_Deprecated/Background/system.grouped.background.colorset/Contents.json new file mode 100644 index 00000000..c915c891 --- /dev/null +++ b/Mastodon/Resources/Assets.xcassets/_Deprecated/Background/system.grouped.background.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.910", + "green" : "0.882", + "red" : "0.851" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.133", + "green" : "0.106", + "red" : "0.098" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Mastodon/Resources/Assets.xcassets/_Deprecated/Background/tertiary.system.background.colorset/Contents.json b/Mastodon/Resources/Assets.xcassets/_Deprecated/Background/tertiary.system.background.colorset/Contents.json new file mode 100644 index 00000000..4572c240 --- /dev/null +++ b/Mastodon/Resources/Assets.xcassets/_Deprecated/Background/tertiary.system.background.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.996", + "green" : "1.000", + "red" : "0.996" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.216", + "green" : "0.173", + "red" : "0.157" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Mastodon/Resources/Assets.xcassets/_Deprecated/Background/tertiary.system.grouped.background.colorset/Contents.json b/Mastodon/Resources/Assets.xcassets/_Deprecated/Background/tertiary.system.grouped.background.colorset/Contents.json new file mode 100644 index 00000000..98dd7bbd --- /dev/null +++ b/Mastodon/Resources/Assets.xcassets/_Deprecated/Background/tertiary.system.grouped.background.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.910", + "green" : "0.882", + "red" : "0.851" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.263", + "green" : "0.208", + "red" : "0.192" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Mastodon/Resources/Assets.xcassets/Scene/Compose/Contents.json b/Mastodon/Resources/Assets.xcassets/_Deprecated/Compose/Contents.json similarity index 100% rename from Mastodon/Resources/Assets.xcassets/Scene/Compose/Contents.json rename to Mastodon/Resources/Assets.xcassets/_Deprecated/Compose/Contents.json diff --git a/Mastodon/Resources/Assets.xcassets/Colors/Background/system.elevated.background.colorset/Contents.json b/Mastodon/Resources/Assets.xcassets/_Deprecated/Compose/background.colorset/Contents.json similarity index 88% rename from Mastodon/Resources/Assets.xcassets/Colors/Background/system.elevated.background.colorset/Contents.json rename to Mastodon/Resources/Assets.xcassets/_Deprecated/Compose/background.colorset/Contents.json index 147cca83..33b71ef9 100644 --- a/Mastodon/Resources/Assets.xcassets/Colors/Background/system.elevated.background.colorset/Contents.json +++ b/Mastodon/Resources/Assets.xcassets/_Deprecated/Compose/background.colorset/Contents.json @@ -23,9 +23,9 @@ "color-space" : "srgb", "components" : { "alpha" : "1.000", - "blue" : "0x37", - "green" : "0x2C", - "red" : "0x28" + "blue" : "0.216", + "green" : "0.173", + "red" : "0.157" } }, "idiom" : "universal" diff --git a/Mastodon/Resources/Assets.xcassets/_Deprecated/Compose/toolbar.background.colorset/Contents.json b/Mastodon/Resources/Assets.xcassets/_Deprecated/Compose/toolbar.background.colorset/Contents.json new file mode 100644 index 00000000..da7b7606 --- /dev/null +++ b/Mastodon/Resources/Assets.xcassets/_Deprecated/Compose/toolbar.background.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.871", + "green" : "0.847", + "red" : "0.839" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "0.920", + "blue" : "0.125", + "green" : "0.125", + "red" : "0.125" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Mastodon/Resources/Assets.xcassets/_Deprecated/Contents.json b/Mastodon/Resources/Assets.xcassets/_Deprecated/Contents.json new file mode 100644 index 00000000..6e965652 --- /dev/null +++ b/Mastodon/Resources/Assets.xcassets/_Deprecated/Contents.json @@ -0,0 +1,9 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "provides-namespace" : true + } +} diff --git a/Mastodon/Resources/ar.lproj/Localizable.strings b/Mastodon/Resources/ar.lproj/Localizable.strings index e7369046..99babb0b 100644 --- a/Mastodon/Resources/ar.lproj/Localizable.strings +++ b/Mastodon/Resources/ar.lproj/Localizable.strings @@ -323,7 +323,7 @@ any server."; "Scene.Settings.Section.Appearance.Title" = "Appearance"; "Scene.Settings.Section.AppearanceSettings.DisableAvatarAnimation" = "Disable animated avatars"; "Scene.Settings.Section.AppearanceSettings.TrueBlackDarkMode" = "True black dark mode"; -"Scene.Settings.Section.BoringZone.AccountSettings" = "Account settings"; +"Scene.Settings.Section.BoringZone.AccountSettings" = "Account Settings"; "Scene.Settings.Section.BoringZone.Privacy" = "Privacy Policy"; "Scene.Settings.Section.BoringZone.Terms" = "Terms of Service"; "Scene.Settings.Section.BoringZone.Title" = "The Boring Zone"; diff --git a/Mastodon/Resources/en.lproj/Localizable.strings b/Mastodon/Resources/en.lproj/Localizable.strings index e7369046..99babb0b 100644 --- a/Mastodon/Resources/en.lproj/Localizable.strings +++ b/Mastodon/Resources/en.lproj/Localizable.strings @@ -323,7 +323,7 @@ any server."; "Scene.Settings.Section.Appearance.Title" = "Appearance"; "Scene.Settings.Section.AppearanceSettings.DisableAvatarAnimation" = "Disable animated avatars"; "Scene.Settings.Section.AppearanceSettings.TrueBlackDarkMode" = "True black dark mode"; -"Scene.Settings.Section.BoringZone.AccountSettings" = "Account settings"; +"Scene.Settings.Section.BoringZone.AccountSettings" = "Account Settings"; "Scene.Settings.Section.BoringZone.Privacy" = "Privacy Policy"; "Scene.Settings.Section.BoringZone.Terms" = "Terms of Service"; "Scene.Settings.Section.BoringZone.Title" = "The Boring Zone"; diff --git a/Mastodon/Scene/Compose/AutoComplete/View/AutoCompleteTopChevronView.swift b/Mastodon/Scene/Compose/AutoComplete/View/AutoCompleteTopChevronView.swift index de409dfc..9c3c81c3 100644 --- a/Mastodon/Scene/Compose/AutoComplete/View/AutoCompleteTopChevronView.swift +++ b/Mastodon/Scene/Compose/AutoComplete/View/AutoCompleteTopChevronView.swift @@ -6,8 +6,11 @@ // import UIKit +import Combine final class AutoCompleteTopChevronView: UIView { + + var disposeBag = Set() static let chevronSize = CGSize(width: 20, height: 12) @@ -16,10 +19,10 @@ final class AutoCompleteTopChevronView: UIView { private let maskLayer = CAShapeLayer() var chevronMinX: CGFloat = 0 - var topViewBackgroundColor = Asset.Scene.Compose.background.color { + var topViewBackgroundColor = ThemeService.shared.currentTheme.value.systemElevatedBackgroundColor { didSet { setNeedsLayout() } } - var bottomViewBackgroundColor = Asset.Colors.Background.systemBackground.color { + var bottomViewBackgroundColor = ThemeService.shared.currentTheme.value.systemBackgroundColor { didSet { setNeedsLayout() } } @@ -70,6 +73,15 @@ extension AutoCompleteTopChevronView { shadowLayer.fillColor = topViewBackgroundColor.cgColor shadowView.layer.addSublayer(shadowLayer) + + setupBackgroundColor(theme: ThemeService.shared.currentTheme.value) + ThemeService.shared.currentTheme + .receive(on: DispatchQueue.main) + .sink { [weak self] theme in + guard let self = self else { return } + self.setupBackgroundColor(theme: theme) + } + .store(in: &disposeBag) } override func layoutSubviews() { @@ -114,6 +126,13 @@ extension AutoCompleteTopChevronView { } +extension AutoCompleteTopChevronView { + private func setupBackgroundColor(theme: Theme) { + topViewBackgroundColor = theme.systemElevatedBackgroundColor + bottomViewBackgroundColor = theme.systemBackgroundColor + } +} + extension AutoCompleteTopChevronView { func invertMask(in rect: CGRect) -> CAShapeLayer { let path = UIBezierPath() @@ -153,7 +172,7 @@ struct AutoCompleteTopChevronView_Previews: PreviewProvider { view.chevronMinX = 10 return view } - .background(Color(Asset.Scene.Compose.background.color)) + .background(Color(ThemeService.shared.currentTheme.value.systemElevatedBackgroundColor)) .padding(20) .previewLayout(.fixed(width: 375 + 40, height: 100 + 40)) UIViewPreview(width: 375) { @@ -166,7 +185,7 @@ struct AutoCompleteTopChevronView_Previews: PreviewProvider { view.chevronMinX = 10 return view } - .background(Color(Asset.Scene.Compose.background.color)) + .background(Color(ThemeService.shared.currentTheme.value.systemElevatedBackgroundColor)) .preferredColorScheme(.dark) .padding(20) .previewLayout(.fixed(width: 375 + 40, height: 100 + 40)) diff --git a/Mastodon/Scene/Compose/CollectionViewCell/ComposeStatusAttachmentCollectionViewCell.swift b/Mastodon/Scene/Compose/CollectionViewCell/ComposeStatusAttachmentCollectionViewCell.swift index 8d8cabcf..fee6ce75 100644 --- a/Mastodon/Scene/Compose/CollectionViewCell/ComposeStatusAttachmentCollectionViewCell.swift +++ b/Mastodon/Scene/Compose/CollectionViewCell/ComposeStatusAttachmentCollectionViewCell.swift @@ -32,10 +32,10 @@ final class ComposeStatusAttachmentCollectionViewCell: UICollectionViewCell { let image = UIImage(systemName: "minus")!.withConfiguration(UIImage.SymbolConfiguration(pointSize: 14, weight: .bold)) button.tintColor = .white button.setImage(image, for: .normal) - button.setBackgroundImage(.placeholder(color: Asset.Colors.Background.danger.color), for: .normal) + button.setBackgroundImage(.placeholder(color: Asset.Colors.danger.color), for: .normal) button.layer.masksToBounds = true button.layer.cornerRadius = ComposeStatusAttachmentCollectionViewCell.removeButtonSize.width * 0.5 - button.layer.borderColor = Asset.Colors.Background.dangerBorder.color.cgColor + button.layer.borderColor = Asset.Colors.dangerBorder.color.cgColor button.layer.borderWidth = 1 return button }() diff --git a/Mastodon/Scene/Compose/ComposeViewController.swift b/Mastodon/Scene/Compose/ComposeViewController.swift index b48bcc63..eb3b95c9 100644 --- a/Mastodon/Scene/Compose/ComposeViewController.swift +++ b/Mastodon/Scene/Compose/ComposeViewController.swift @@ -76,11 +76,7 @@ final class ComposeViewController: UIViewController, NeedsDependency { let composeToolbarView = ComposeToolbarView() var composeToolbarViewBottomLayoutConstraint: NSLayoutConstraint! - let composeToolbarBackgroundView: UIView = { - let view = UIView() - view.backgroundColor = Asset.Scene.Compose.toolbarBackground.color - return view - }() + let composeToolbarBackgroundView = UIView() static func createPhotoLibraryPickerConfiguration(selectionLimit: Int = 4) -> PHPickerConfiguration { var configuration = PHPickerConfiguration() @@ -591,6 +587,7 @@ extension ComposeViewController { private func setupBackgroundColor(theme: Theme) { view.backgroundColor = theme.systemElevatedBackgroundColor tableView.backgroundColor = theme.systemElevatedBackgroundColor + composeToolbarBackgroundView.backgroundColor = theme.composeToolbarBackgroundColor } } diff --git a/Mastodon/Scene/Compose/TableViewCell/ComposeStatusAttachmentTableViewCell.swift b/Mastodon/Scene/Compose/TableViewCell/ComposeStatusAttachmentTableViewCell.swift index 623ec717..561c6c3b 100644 --- a/Mastodon/Scene/Compose/TableViewCell/ComposeStatusAttachmentTableViewCell.swift +++ b/Mastodon/Scene/Compose/TableViewCell/ComposeStatusAttachmentTableViewCell.swift @@ -88,7 +88,7 @@ extension ComposeStatusAttachmentTableViewCell { guard let image = thumbnailImage else { let placeholder = UIImage.placeholder( size: size, - color: Asset.Colors.Background.systemGroupedBackground.color + color: ThemeService.shared.currentTheme.value.systemGroupedBackgroundColor ) .af.imageRounded( withCornerRadius: AttachmentContainerView.containerViewCornerRadius diff --git a/Mastodon/Scene/Compose/View/ComposeToolbarView.swift b/Mastodon/Scene/Compose/View/ComposeToolbarView.swift index aecd5d06..c1cda22a 100644 --- a/Mastodon/Scene/Compose/View/ComposeToolbarView.swift +++ b/Mastodon/Scene/Compose/View/ComposeToolbarView.swift @@ -96,8 +96,15 @@ final class ComposeToolbarView: UIView { extension ComposeToolbarView { private func _init() { - backgroundColor = Asset.Scene.Compose.toolbarBackground.color - + setupBackgroundColor(theme: ThemeService.shared.currentTheme.value) + ThemeService.shared.currentTheme + .receive(on: DispatchQueue.main) + .sink { [weak self] theme in + guard let self = self else { return } + self.setupBackgroundColor(theme: theme) + } + .store(in: &disposeBag) + let stackView = UIStackView() stackView.axis = .horizontal stackView.spacing = 0 @@ -212,6 +219,10 @@ extension ComposeToolbarView { extension ComposeToolbarView { + private func setupBackgroundColor(theme: Theme) { + backgroundColor = theme.composeToolbarBackgroundColor + } + private static func configureToolbarButtonAppearance(button: UIButton) { button.tintColor = Asset.Colors.brandBlue.color button.setBackgroundImage(.placeholder(size: ComposeToolbarView.toolbarButtonSize, color: .systemFill), for: .highlighted) diff --git a/Mastodon/Scene/HomeTimeline/View/HomeTimelineNavigationBarTitleView.swift b/Mastodon/Scene/HomeTimeline/View/HomeTimelineNavigationBarTitleView.swift index caa2fbe0..1e9c020c 100644 --- a/Mastodon/Scene/HomeTimeline/View/HomeTimelineNavigationBarTitleView.swift +++ b/Mastodon/Scene/HomeTimeline/View/HomeTimelineNavigationBarTitleView.swift @@ -114,7 +114,7 @@ extension HomeTimelineNavigationBarTitleView { configureButton( title: L10n.Scene.HomeTimeline.NavigationBarState.offline, textColor: .white, - backgroundColor: Asset.Colors.Background.danger.color + backgroundColor: Asset.Colors.danger.color ) button.isHidden = false case .publishingPostLabel: diff --git a/Mastodon/Scene/Notification/TableViewCell/NotificationStatusTableViewCell.swift b/Mastodon/Scene/Notification/TableViewCell/NotificationStatusTableViewCell.swift index f4987dc4..9c3558ea 100644 --- a/Mastodon/Scene/Notification/TableViewCell/NotificationStatusTableViewCell.swift +++ b/Mastodon/Scene/Notification/TableViewCell/NotificationStatusTableViewCell.swift @@ -54,13 +54,13 @@ final class NotificationStatusTableViewCell: UITableViewCell, StatusCell { let actionImageView: UIImageView = { let imageView = UIImageView() imageView.contentMode = .center - imageView.tintColor = Asset.Colors.Background.systemBackground.color + imageView.tintColor = Asset.Theme.Mastodon.systemBackground.color imageView.isOpaque = true imageView.layer.masksToBounds = true imageView.layer.cornerRadius = NotificationStatusTableViewCell.actionImageViewSize.width * 0.5 imageView.layer.cornerCurve = .circular imageView.layer.borderWidth = NotificationStatusTableViewCell.actionImageBorderWidth - imageView.layer.borderColor = Asset.Colors.Background.systemBackground.color.cgColor + imageView.layer.borderColor = Asset.Theme.Mastodon.systemBackground.color.cgColor imageView.layer.shouldRasterize = true imageView.layer.rasterizationScale = UIScreen.main.scale return imageView @@ -288,7 +288,7 @@ extension NotificationStatusTableViewCell { super.traitCollectionDidChange(previousTraitCollection) resetSeparatorLineLayout() - avatarImageView.layer.borderColor = Asset.Colors.Background.systemBackground.color.cgColor + avatarImageView.layer.borderColor = Asset.Theme.Mastodon.systemBackground.color.cgColor statusContainerView.layer.borderColor = Asset.Colors.Border.notificationStatus.color.cgColor } diff --git a/Mastodon/Scene/Onboarding/PickServer/MastodonPickServerViewController.swift b/Mastodon/Scene/Onboarding/PickServer/MastodonPickServerViewController.swift index 012f173d..70c1067f 100644 --- a/Mastodon/Scene/Onboarding/PickServer/MastodonPickServerViewController.swift +++ b/Mastodon/Scene/Onboarding/PickServer/MastodonPickServerViewController.swift @@ -115,7 +115,7 @@ extension MastodonPickServerViewController { tableViewTopPaddingView.trailingAnchor.constraint(equalTo: view.trailingAnchor), tableViewTopPaddingViewHeightLayoutConstraint, ]) - tableViewTopPaddingView.backgroundColor = Asset.Colors.Background.systemGroupedBackground.color + tableViewTopPaddingView.backgroundColor = Asset.Theme.Mastodon.systemGroupedBackground.color view.addSubview(tableView) NSLayoutConstraint.activate([ @@ -422,7 +422,7 @@ extension MastodonPickServerViewController: UITableViewDelegate { func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { let headerView = UIView() - headerView.backgroundColor = Asset.Colors.Background.systemGroupedBackground.color + headerView.backgroundColor = Asset.Theme.Mastodon.systemGroupedBackground.color return headerView } diff --git a/Mastodon/Scene/Onboarding/PickServer/TableViewCell/PickServerCategoriesCell.swift b/Mastodon/Scene/Onboarding/PickServer/TableViewCell/PickServerCategoriesCell.swift index 373a90dd..8207ccdb 100644 --- a/Mastodon/Scene/Onboarding/PickServer/TableViewCell/PickServerCategoriesCell.swift +++ b/Mastodon/Scene/Onboarding/PickServer/TableViewCell/PickServerCategoriesCell.swift @@ -55,7 +55,7 @@ extension PickServerCategoriesCell { private func _init() { selectionStyle = .none - backgroundColor = Asset.Colors.Background.systemGroupedBackground.color + backgroundColor = Asset.Theme.Mastodon.systemGroupedBackground.color metricView.translatesAutoresizingMaskIntoConstraints = false contentView.addSubview(metricView) diff --git a/Mastodon/Scene/Onboarding/PickServer/TableViewCell/PickServerCell.swift b/Mastodon/Scene/Onboarding/PickServer/TableViewCell/PickServerCell.swift index ca69aef2..41cf8d38 100644 --- a/Mastodon/Scene/Onboarding/PickServer/TableViewCell/PickServerCell.swift +++ b/Mastodon/Scene/Onboarding/PickServer/TableViewCell/PickServerCell.swift @@ -27,7 +27,7 @@ class PickServerCell: UITableViewCell { let containerView: UIView = { let view = UIView() view.layoutMargins = UIEdgeInsets(top: 16, left: 16, bottom: 10, right: 16) - view.backgroundColor = Asset.Colors.Background.secondaryGroupedSystemBackground.color + view.backgroundColor = Asset.Theme.Mastodon.secondaryGroupedSystemBackground.color view.translatesAutoresizingMaskIntoConstraints = false return view }() @@ -101,7 +101,7 @@ class PickServerCell: UITableViewCell { let separator: UIView = { let view = UIView() - view.backgroundColor = Asset.Colors.Background.systemGroupedBackground.color + view.backgroundColor = Asset.Theme.Mastodon.systemGroupedBackground.color view.translatesAutoresizingMaskIntoConstraints = false return view }() diff --git a/Mastodon/Scene/Onboarding/PickServer/TableViewCell/PickServerLoaderTableViewCell.swift b/Mastodon/Scene/Onboarding/PickServer/TableViewCell/PickServerLoaderTableViewCell.swift index 37135fa9..1b8264ec 100644 --- a/Mastodon/Scene/Onboarding/PickServer/TableViewCell/PickServerLoaderTableViewCell.swift +++ b/Mastodon/Scene/Onboarding/PickServer/TableViewCell/PickServerLoaderTableViewCell.swift @@ -13,14 +13,14 @@ final class PickServerLoaderTableViewCell: TimelineLoaderTableViewCell { let containerView: UIView = { let view = UIView() view.layoutMargins = UIEdgeInsets(top: 16, left: 16, bottom: 10, right: 16) - view.backgroundColor = Asset.Colors.Background.secondaryGroupedSystemBackground.color + view.backgroundColor = Asset.Theme.Mastodon.secondaryGroupedSystemBackground.color view.translatesAutoresizingMaskIntoConstraints = false return view }() let seperator: UIView = { let view = UIView() - view.backgroundColor = Asset.Colors.Background.systemGroupedBackground.color + view.backgroundColor = Asset.Theme.Mastodon.systemGroupedBackground.color view.translatesAutoresizingMaskIntoConstraints = false return view }() diff --git a/Mastodon/Scene/Onboarding/PickServer/TableViewCell/PickServerSearchCell.swift b/Mastodon/Scene/Onboarding/PickServer/TableViewCell/PickServerSearchCell.swift index 61330452..fa3e3ae2 100644 --- a/Mastodon/Scene/Onboarding/PickServer/TableViewCell/PickServerSearchCell.swift +++ b/Mastodon/Scene/Onboarding/PickServer/TableViewCell/PickServerSearchCell.swift @@ -17,7 +17,7 @@ class PickServerSearchCell: UITableViewCell { private var bgView: UIView = { let view = UIView() - view.backgroundColor = Asset.Colors.Background.secondaryGroupedSystemBackground.color + view.backgroundColor = Asset.Theme.Mastodon.secondaryGroupedSystemBackground.color view.translatesAutoresizingMaskIntoConstraints = false view.layer.maskedCorners = [ .layerMinXMinYCorner, @@ -108,7 +108,7 @@ class PickServerSearchCell: UITableViewCell { extension PickServerSearchCell { private func _init() { selectionStyle = .none - backgroundColor = Asset.Colors.Background.systemGroupedBackground.color + backgroundColor = Asset.Theme.Mastodon.systemGroupedBackground.color searchTextField.addTarget(self, action: #selector(textFieldDidChange(_:)), for: .editingChanged) searchTextField.delegate = self diff --git a/Mastodon/Scene/Onboarding/PickServer/TableViewCell/PickServerTitleCell.swift b/Mastodon/Scene/Onboarding/PickServer/TableViewCell/PickServerTitleCell.swift index 30d24ddc..2cfd5ec3 100644 --- a/Mastodon/Scene/Onboarding/PickServer/TableViewCell/PickServerTitleCell.swift +++ b/Mastodon/Scene/Onboarding/PickServer/TableViewCell/PickServerTitleCell.swift @@ -35,7 +35,7 @@ extension PickServerTitleCell { private func _init() { selectionStyle = .none - backgroundColor = Asset.Colors.Background.systemGroupedBackground.color + backgroundColor = Asset.Theme.Mastodon.systemBackground.color contentView.addSubview(titleLabel) NSLayoutConstraint.activate([ diff --git a/Mastodon/Scene/Onboarding/PickServer/View/PickServerCategoryView.swift b/Mastodon/Scene/Onboarding/PickServer/View/PickServerCategoryView.swift index fd1a3ea6..6565fbcf 100644 --- a/Mastodon/Scene/Onboarding/PickServer/View/PickServerCategoryView.swift +++ b/Mastodon/Scene/Onboarding/PickServer/View/PickServerCategoryView.swift @@ -48,7 +48,7 @@ extension PickServerCategoryView { addSubview(bgView) addSubview(titleLabel) - bgView.backgroundColor = Asset.Colors.Background.secondaryGroupedSystemBackground.color + bgView.backgroundColor = Asset.Theme.Mastodon.secondaryGroupedSystemBackground.color NSLayoutConstraint.activate([ bgView.leadingAnchor.constraint(equalTo: self.leadingAnchor), diff --git a/Mastodon/Scene/Onboarding/PickServer/View/PickServerEmptyStateView.swift b/Mastodon/Scene/Onboarding/PickServer/View/PickServerEmptyStateView.swift index 6a30751a..1d2c17c7 100644 --- a/Mastodon/Scene/Onboarding/PickServer/View/PickServerEmptyStateView.swift +++ b/Mastodon/Scene/Onboarding/PickServer/View/PickServerEmptyStateView.swift @@ -44,7 +44,7 @@ final class PickServerEmptyStateView: UIView { extension PickServerEmptyStateView { private func _init() { - backgroundColor = Asset.Colors.Background.secondaryGroupedSystemBackground.color + backgroundColor = Asset.Theme.Mastodon.secondaryGroupedSystemBackground.color layer.maskedCorners = [ .layerMinXMaxYCorner, .layerMaxXMaxYCorner diff --git a/Mastodon/Scene/Onboarding/Register/MastodonRegisterViewController.swift b/Mastodon/Scene/Onboarding/Register/MastodonRegisterViewController.swift index 1a9043ef..5ea418e4 100644 --- a/Mastodon/Scene/Onboarding/Register/MastodonRegisterViewController.swift +++ b/Mastodon/Scene/Onboarding/Register/MastodonRegisterViewController.swift @@ -84,7 +84,7 @@ final class MastodonRegisterViewController: UIViewController, NeedsDependency, O button.setImage(image?.withRenderingMode(UIImage.RenderingMode.alwaysTemplate), for: UIControl.State.normal) button.imageView?.tintColor = Asset.Colors.Label.secondary.color - button.backgroundColor = Asset.Colors.Background.secondaryGroupedSystemBackground.color + button.backgroundColor = Asset.Theme.Mastodon.secondaryGroupedSystemBackground.color button.layer.cornerRadius = 10 button.clipsToBounds = true @@ -99,7 +99,7 @@ final class MastodonRegisterViewController: UIViewController, NeedsDependency, O icon.backgroundColor = UIColor(dynamicProvider: { collection in switch collection.userInterfaceStyle { case .dark: - return Asset.Colors.Background.secondaryGroupedSystemBackground.color + return Asset.Theme.Mastodon.secondaryGroupedSystemBackground.color default: return .white } @@ -119,7 +119,7 @@ final class MastodonRegisterViewController: UIViewController, NeedsDependency, O textField.returnKeyType = .next textField.autocapitalizationType = .none textField.autocorrectionType = .no - textField.backgroundColor = Asset.Colors.Background.secondaryGroupedSystemBackground.color + textField.backgroundColor = Asset.Theme.Mastodon.secondaryGroupedSystemBackground.color textField.textColor = Asset.Colors.Label.primary.color textField.attributedPlaceholder = NSAttributedString(string: L10n.Scene.Register.Input.Username.placeholder, attributes: [NSAttributedString.Key.foregroundColor: Asset.Colors.Label.secondary.color, @@ -170,7 +170,7 @@ final class MastodonRegisterViewController: UIViewController, NeedsDependency, O textField.returnKeyType = .next textField.autocapitalizationType = .none textField.autocorrectionType = .no - textField.backgroundColor = Asset.Colors.Background.secondaryGroupedSystemBackground.color + textField.backgroundColor = Asset.Theme.Mastodon.secondaryGroupedSystemBackground.color textField.textColor = Asset.Colors.Label.primary.color textField.attributedPlaceholder = NSAttributedString(string: L10n.Scene.Register.Input.DisplayName.placeholder, attributes: [NSAttributedString.Key.foregroundColor: Asset.Colors.Label.secondary.color, @@ -189,7 +189,7 @@ final class MastodonRegisterViewController: UIViewController, NeedsDependency, O textField.autocapitalizationType = .none textField.autocorrectionType = .no textField.keyboardType = .emailAddress - textField.backgroundColor = Asset.Colors.Background.secondaryGroupedSystemBackground.color + textField.backgroundColor = Asset.Theme.Mastodon.secondaryGroupedSystemBackground.color textField.textColor = Asset.Colors.Label.primary.color textField.attributedPlaceholder = NSAttributedString(string: L10n.Scene.Register.Input.Email.placeholder, attributes: [NSAttributedString.Key.foregroundColor: Asset.Colors.Label.secondary.color, @@ -216,7 +216,7 @@ final class MastodonRegisterViewController: UIViewController, NeedsDependency, O textField.autocorrectionType = .no textField.keyboardType = .asciiCapable textField.isSecureTextEntry = true - textField.backgroundColor = Asset.Colors.Background.secondaryGroupedSystemBackground.color + textField.backgroundColor = Asset.Theme.Mastodon.secondaryGroupedSystemBackground.color textField.textColor = Asset.Colors.Label.primary.color textField.attributedPlaceholder = NSAttributedString(string: L10n.Scene.Register.Input.Password.placeholder, attributes: [NSAttributedString.Key.foregroundColor: Asset.Colors.Label.secondary.color, @@ -248,7 +248,7 @@ final class MastodonRegisterViewController: UIViewController, NeedsDependency, O textField.returnKeyType = .next // set to "Return" depends on if the last input field or not textField.autocapitalizationType = .none textField.autocorrectionType = .no - textField.backgroundColor = Asset.Colors.Background.secondaryGroupedSystemBackground.color + textField.backgroundColor = Asset.Theme.Mastodon.secondaryGroupedSystemBackground.color textField.textColor = Asset.Colors.Label.primary.color textField.attributedPlaceholder = NSAttributedString(string: L10n.Scene.Register.Input.Invite.registrationUserInviteRequest, attributes: [NSAttributedString.Key.foregroundColor: Asset.Colors.Label.secondary.color, diff --git a/Mastodon/Scene/Onboarding/ServerRules/MastodonServerRulesViewController.swift b/Mastodon/Scene/Onboarding/ServerRules/MastodonServerRulesViewController.swift index fda79091..3faa3a83 100644 --- a/Mastodon/Scene/Onboarding/ServerRules/MastodonServerRulesViewController.swift +++ b/Mastodon/Scene/Onboarding/ServerRules/MastodonServerRulesViewController.swift @@ -49,7 +49,7 @@ final class MastodonServerRulesViewController: UIViewController, NeedsDependency let bottomContainerView: UIView = { let view = UIView() - view.backgroundColor = Asset.Colors.Background.systemGroupedBackground.color + view.backgroundColor = Asset.Theme.Mastodon.systemGroupedBackground.color return view }() @@ -60,7 +60,7 @@ final class MastodonServerRulesViewController: UIViewController, NeedsDependency textView.isSelectable = true textView.isEditable = false textView.isScrollEnabled = false - textView.backgroundColor = Asset.Colors.Background.systemGroupedBackground.color + textView.backgroundColor = Asset.Theme.Mastodon.systemGroupedBackground.color return textView }() diff --git a/Mastodon/Scene/Onboarding/Share/OnboardingViewControllerAppearance.swift b/Mastodon/Scene/Onboarding/Share/OnboardingViewControllerAppearance.swift index 0784b51e..d93c677f 100644 --- a/Mastodon/Scene/Onboarding/Share/OnboardingViewControllerAppearance.swift +++ b/Mastodon/Scene/Onboarding/Share/OnboardingViewControllerAppearance.swift @@ -20,7 +20,7 @@ extension OnboardingViewControllerAppearance { static var viewBottomPaddingHeight: CGFloat { return 11 } func setupOnboardingAppearance() { - view.backgroundColor = Asset.Colors.Background.systemGroupedBackground.color + view.backgroundColor = Asset.Theme.Mastodon.systemGroupedBackground.color setupNavigationBarAppearance() @@ -42,7 +42,7 @@ extension OnboardingViewControllerAppearance { func setupNavigationBarBackgroundView() { let navigationBarBackgroundView: UIView = { let view = UIView() - view.backgroundColor = Asset.Colors.Background.systemGroupedBackground.color + view.backgroundColor = Asset.Theme.Mastodon.systemGroupedBackground.color return view }() diff --git a/Mastodon/Scene/Profile/Header/View/ProfileHeaderView.swift b/Mastodon/Scene/Profile/Header/View/ProfileHeaderView.swift index 509e6739..86975a8d 100644 --- a/Mastodon/Scene/Profile/Header/View/ProfileHeaderView.swift +++ b/Mastodon/Scene/Profile/Header/View/ProfileHeaderView.swift @@ -76,7 +76,7 @@ final class ProfileHeaderView: UIView { let avatarImageView: UIImageView = { let imageView = FLAnimatedImageView() let placeholderImage = UIImage - .placeholder(size: ProfileHeaderView.avatarImageViewSize, color: Asset.Colors.Background.systemGroupedBackground.color) + .placeholder(size: ProfileHeaderView.avatarImageViewSize, color: Asset.Theme.Mastodon.systemGroupedBackground.color) .af.imageRounded(withCornerRadius: ProfileHeaderView.avatarImageViewCornerRadius, divideRadiusByImageScale: false) imageView.image = placeholderImage return imageView diff --git a/Mastodon/Scene/Profile/ProfileViewModel.swift b/Mastodon/Scene/Profile/ProfileViewModel.swift index 05db1a88..4b2809a3 100644 --- a/Mastodon/Scene/Profile/ProfileViewModel.swift +++ b/Mastodon/Scene/Profile/ProfileViewModel.swift @@ -441,9 +441,9 @@ extension ProfileViewModel { case .request: return Asset.Colors.brandBlue.color case .pending: return Asset.Colors.brandBlue.color case .following: return Asset.Colors.brandBlue.color - case .muting: return Asset.Colors.Background.alertYellow.color + case .muting: return Asset.Colors.alertYellow.color case .blocked: return Asset.Colors.brandBlue.color - case .blocking: return Asset.Colors.Background.danger.color + case .blocking: return Asset.Colors.danger.color case .suspended: return Asset.Colors.brandBlue.color case .edit: return Asset.Colors.brandBlue.color case .editing: return Asset.Colors.brandBlue.color diff --git a/Mastodon/Scene/Share/View/Container/PlayerContainerView+MediaTypeIndicotorView.swift b/Mastodon/Scene/Share/View/Container/PlayerContainerView+MediaTypeIndicotorView.swift index 3210fadf..2c029814 100644 --- a/Mastodon/Scene/Share/View/Container/PlayerContainerView+MediaTypeIndicotorView.swift +++ b/Mastodon/Scene/Share/View/Container/PlayerContainerView+MediaTypeIndicotorView.swift @@ -63,7 +63,7 @@ extension PlayerContainerView { extension PlayerContainerView.MediaTypeIndicatorView { private func _init() { - backgroundColor = Asset.Colors.Background.mediaTypeIndicotor.color + backgroundColor = Asset.Colors.mediaTypeIndicotor.color layoutMargins = UIEdgeInsets(top: 3, left: 13, bottom: 0, right: 6) addSubview(label) diff --git a/Mastodon/Scene/Share/View/Content/PollOptionView.swift b/Mastodon/Scene/Share/View/Content/PollOptionView.swift index 6dcbcd61..db84b95d 100644 --- a/Mastodon/Scene/Share/View/Content/PollOptionView.swift +++ b/Mastodon/Scene/Share/View/Content/PollOptionView.swift @@ -29,7 +29,8 @@ final class PollOptionView: UIView { let checkmarkBackgroundView: UIView = { let view = UIView() - view.backgroundColor = Asset.Colors.Background.tertiarySystemBackground.color + // FIXME: missing update trigger + view.backgroundColor = ThemeService.shared.currentTheme.value.tertiarySystemBackgroundColor return view }() diff --git a/Mastodon/Scene/Share/View/TableviewCell/ThreadReplyLoaderTableViewCell.swift b/Mastodon/Scene/Share/View/TableviewCell/ThreadReplyLoaderTableViewCell.swift index ee9ac343..5e5ac88d 100644 --- a/Mastodon/Scene/Share/View/TableviewCell/ThreadReplyLoaderTableViewCell.swift +++ b/Mastodon/Scene/Share/View/TableviewCell/ThreadReplyLoaderTableViewCell.swift @@ -58,7 +58,6 @@ extension ThreadReplyLoaderTableViewCell { func _init() { selectionStyle = .none - backgroundColor = Asset.Colors.Background.systemGroupedBackground.color loadMoreButton.translatesAutoresizingMaskIntoConstraints = false contentView.addSubview(loadMoreButton) @@ -124,7 +123,7 @@ extension ThreadReplyLoaderTableViewCell { } private func setupBackgroundColor(theme: Theme) { - loadMoreButton.backgroundColor = theme.secondarySystemGroupedBackgroundColor + backgroundColor = theme.systemGroupedBackgroundColor } } diff --git a/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewController.swift b/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewController.swift index 3d66aed3..d27c1fbe 100644 --- a/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewController.swift +++ b/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewController.swift @@ -32,7 +32,7 @@ class SuggestionAccountViewController: UIViewController, NeedsDependency { lazy var tableHeader: UIView = { let view = UIView() - view.backgroundColor = Asset.Colors.Background.systemGroupedBackground.color + view.backgroundColor = ThemeService.shared.currentTheme.value.systemGroupedBackgroundColor view.frame = CGRect(origin: .zero, size: CGSize(width: tableView.frame.width, height: 156)) return view }() @@ -67,12 +67,12 @@ extension SuggestionAccountViewController { override func viewDidLoad() { super.viewDidLoad() - view.backgroundColor = ThemeService.shared.currentTheme.value.systemBackgroundColor + setupBackgroundColor(theme: ThemeService.shared.currentTheme.value) ThemeService.shared.currentTheme .receive(on: RunLoop.main) .sink { [weak self] theme in guard let self = self else { return } - self.view.backgroundColor = theme.systemBackgroundColor + self.setupBackgroundColor(theme: theme) } .store(in: &disposeBag) @@ -146,6 +146,11 @@ extension SuggestionAccountViewController { tableView.tableHeaderView = tableHeader } + + private func setupBackgroundColor(theme: Theme) { + view.backgroundColor = theme.systemBackgroundColor + tableHeader.backgroundColor = theme.systemGroupedBackgroundColor + } } extension SuggestionAccountViewController: UICollectionViewDelegateFlowLayout { diff --git a/Mastodon/Service/ThemeService/MastodonTheme.swift b/Mastodon/Service/ThemeService/MastodonTheme.swift index 6476d5a7..2418877f 100644 --- a/Mastodon/Service/ThemeService/MastodonTheme.swift +++ b/Mastodon/Service/ThemeService/MastodonTheme.swift @@ -33,4 +33,5 @@ struct MastodonTheme: Theme { let contentWarningOverlayBackgroundColor = Asset.Theme.Mastodon.contentWarningOverlayBackground.color let profileFieldCollectionViewBackgroundColor = Asset.Theme.Mastodon.profileFieldCollectionViewBackground.color + let composeToolbarBackgroundColor = Asset.Theme.Mastodon.composeToolbarBackground.color } diff --git a/Mastodon/Service/ThemeService/SystemTheme.swift b/Mastodon/Service/ThemeService/SystemTheme.swift index 4a7a1f96..b91ba1ca 100644 --- a/Mastodon/Service/ThemeService/SystemTheme.swift +++ b/Mastodon/Service/ThemeService/SystemTheme.swift @@ -33,4 +33,5 @@ struct SystemTheme: Theme { let contentWarningOverlayBackgroundColor = Asset.Theme.System.contentWarningOverlayBackground.color let profileFieldCollectionViewBackgroundColor = Asset.Theme.System.profileFieldCollectionViewBackground.color + let composeToolbarBackgroundColor = Asset.Theme.System.composeToolbarBackground.color } diff --git a/Mastodon/Service/ThemeService/Theme.swift b/Mastodon/Service/ThemeService/Theme.swift index c9378e87..29f90db8 100644 --- a/Mastodon/Service/ThemeService/Theme.swift +++ b/Mastodon/Service/ThemeService/Theme.swift @@ -8,6 +8,7 @@ import UIKit public protocol Theme { + var systemBackgroundColor: UIColor { get } var secondarySystemBackgroundColor: UIColor { get } var tertiarySystemBackgroundColor: UIColor { get } @@ -33,6 +34,7 @@ public protocol Theme { var contentWarningOverlayBackgroundColor: UIColor { get } var profileFieldCollectionViewBackgroundColor: UIColor { get } + var composeToolbarBackgroundColor: UIColor { get } } diff --git a/ShareActionExtension/Scene/ShareViewController.swift b/ShareActionExtension/Scene/ShareViewController.swift index a3cef66d..e72c4619 100644 --- a/ShareActionExtension/Scene/ShareViewController.swift +++ b/ShareActionExtension/Scene/ShareViewController.swift @@ -49,11 +49,7 @@ class ShareViewController: UIViewController { let viewSafeAreaDidChange = PassthroughSubject() let composeToolbarView = ComposeToolbarView() var composeToolbarViewBottomLayoutConstraint: NSLayoutConstraint! - let composeToolbarBackgroundView: UIView = { - let view = UIView() - view.backgroundColor = Asset.Scene.Compose.toolbarBackground.color - return view - }() + let composeToolbarBackgroundView = UIView() } extension ShareViewController { @@ -61,7 +57,16 @@ extension ShareViewController { override func viewDidLoad() { super.viewDidLoad() - view.backgroundColor = Asset.Colors.Background.systemBackground.color + navigationController?.presentationController?.delegate = self + + setupBackgroundColor(theme: ThemeService.shared.currentTheme.value) + ThemeService.shared.currentTheme + .receive(on: DispatchQueue.main) + .sink { [weak self] theme in + guard let self = self else { return } + self.setupBackgroundColor(theme: theme) + } + .store(in: &disposeBag) navigationItem.leftBarButtonItem = cancelBarButtonItem viewModel.isBusy @@ -203,10 +208,37 @@ extension ShareViewController { } +extension ShareViewController { + private func setupBackgroundColor(theme: Theme) { + view.backgroundColor = theme.systemElevatedBackgroundColor + viewModel.composeViewModel.backgroundColor = theme.systemElevatedBackgroundColor + composeToolbarBackgroundView.backgroundColor = theme.composeToolbarBackgroundColor + + let barAppearance = UINavigationBarAppearance() + barAppearance.configureWithDefaultBackground() + barAppearance.backgroundColor = theme.navigationBarBackgroundColor + navigationItem.standardAppearance = barAppearance + navigationItem.compactAppearance = barAppearance + navigationItem.scrollEdgeAppearance = barAppearance + } + + private func showDismissConfirmAlertController() { + let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) // can not use alert in extension + let discardAction = UIAlertAction(title: L10n.Common.Controls.Actions.discard, style: .destructive) { _ in + self.extensionContext?.cancelRequest(withError: ShareViewModel.ShareError.userCancelShare) + } + alertController.addAction(discardAction) + let okAction = UIAlertAction(title: L10n.Common.Controls.Actions.ok, style: .cancel, handler: nil) + alertController.addAction(okAction) + self.present(alertController, animated: true, completion: nil) + } +} + extension ShareViewController { @objc private func cancelBarButtonItemPressed(_ sender: UIBarButtonItem) { logger.debug("\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public)") - extensionContext?.cancelRequest(withError: ShareViewModel.ShareError.userCancelShare) + + showDismissConfirmAlertController() } @objc private func publishBarButtonItemPressed(_ sender: UIBarButtonItem) { @@ -267,5 +299,23 @@ extension ShareViewController: ComposeToolbarViewDelegate { viewModel.selectedStatusVisibility.value = type } +} + +// MARK: - UIAdaptivePresentationControllerDelegate +extension ShareViewController: UIAdaptivePresentationControllerDelegate { + + func presentationControllerShouldDismiss(_ presentationController: UIPresentationController) -> Bool { + return viewModel.shouldDismiss.value + } + + func presentationControllerDidAttemptToDismiss(_ presentationController: UIPresentationController) { + os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function) + showDismissConfirmAlertController() + + } + + func presentationControllerDidDismiss(_ presentationController: UIPresentationController) { + os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function) + } } diff --git a/ShareActionExtension/Scene/ShareViewModel.swift b/ShareActionExtension/Scene/ShareViewModel.swift index 060181ae..62102e66 100644 --- a/ShareActionExtension/Scene/ShareViewModel.swift +++ b/ShareActionExtension/Scene/ShareViewModel.swift @@ -37,6 +37,7 @@ final class ShareViewModel { let isPublishing = CurrentValueSubject(false) let isBusy = CurrentValueSubject(true) let isValid = CurrentValueSubject(false) + let shouldDismiss = CurrentValueSubject(true) let composeViewModel = ComposeViewModel() let characterCount = CurrentValueSubject(0) @@ -134,6 +135,11 @@ final class ShareViewModel { // bind compose bar button item UI state let isComposeContentEmpty = composeViewModel.$statusContent .map { $0.isEmpty } + + isComposeContentEmpty + .assign(to: \.value, on: shouldDismiss) + .store(in: &disposeBag) + let isComposeContentValid = composeViewModel.$characterCount .map { characterCount -> Bool in return characterCount <= ShareViewModel.composeContentLimit diff --git a/ShareActionExtension/Scene/View/ComposeToolbarView.swift b/ShareActionExtension/Scene/View/ComposeToolbarView.swift index e938ebfa..d161e96f 100644 --- a/ShareActionExtension/Scene/View/ComposeToolbarView.swift +++ b/ShareActionExtension/Scene/View/ComposeToolbarView.swift @@ -67,7 +67,14 @@ final class ComposeToolbarView: UIView { extension ComposeToolbarView { private func _init() { - backgroundColor = Asset.Scene.Compose.toolbarBackground.color + setupBackgroundColor(theme: ThemeService.shared.currentTheme.value) + ThemeService.shared.currentTheme + .receive(on: DispatchQueue.main) + .sink { [weak self] theme in + guard let self = self else { return } + self.setupBackgroundColor(theme: theme) + } + .store(in: &disposeBag) let stackView = UIStackView() stackView.axis = .horizontal @@ -127,6 +134,12 @@ extension ComposeToolbarView { } +extension ComposeToolbarView { + private func setupBackgroundColor(theme: Theme) { + backgroundColor = theme.composeToolbarBackgroundColor + } +} + extension ComposeToolbarView { enum MediaSelectionType: String { case camera diff --git a/ShareActionExtension/Scene/View/ComposeView.swift b/ShareActionExtension/Scene/View/ComposeView.swift index f65e35b4..25adf4c5 100644 --- a/ShareActionExtension/Scene/View/ComposeView.swift +++ b/ShareActionExtension/Scene/View/ComposeView.swift @@ -29,7 +29,7 @@ public struct ComposeView: View { .padding(EdgeInsets(top: 6, leading: horizontalMargin, bottom: 6, trailing: horizontalMargin)) .background(viewModel.contentWarningBackgroundColor) .transition(.opacity) - .listRow() + .listRow(backgroundColor: Color(viewModel.backgroundColor)) } // Author @@ -39,7 +39,7 @@ public struct ComposeView: View { username: viewModel.authorUsername ) .padding(EdgeInsets(top: 20, leading: horizontalMargin, bottom: 16, trailing: horizontalMargin)) - .listRow() + .listRow(backgroundColor: Color(viewModel.backgroundColor)) // Editor StatusEditorView( @@ -53,7 +53,7 @@ public struct ComposeView: View { .frame(width: statusEditorViewWidth) .frame(minHeight: 100) .padding(EdgeInsets(top: 0, leading: horizontalMargin, bottom: 0, trailing: horizontalMargin)) - .listRow() + .listRow(backgroundColor: Color(viewModel.backgroundColor)) // Attachments ForEach(viewModel.attachmentViewModels) { attachmentViewModel in @@ -78,12 +78,12 @@ public struct ComposeView: View { } .padding(EdgeInsets(top: 16, leading: horizontalMargin, bottom: 0, trailing: horizontalMargin)) .fixedSize(horizontal: false, vertical: true) - .listRow() + .listRow(backgroundColor: Color(viewModel.backgroundColor)) // bottom padding Color.clear .frame(height: viewModel.toolbarHeight + 20) - .listRow() + .listRow(backgroundColor: Color(viewModel.backgroundColor)) } // end List .introspectTableView(customize: { tableView in // tableView.keyboardDismissMode = .onDrag @@ -97,9 +97,13 @@ public struct ComposeView: View { var frame = frame frame.size.width = frame.width - 2 * horizontalMargin statusEditorViewWidth = frame.width - } - } - } + } // end List + .introspectTableView(customize: { tableView in + tableView.backgroundColor = .clear + }) + .background(Color(viewModel.backgroundColor).ignoresSafeArea()) + } // end GeometryReader + } // end body } struct ComposeListViewFramePreferenceKey: PreferenceKey { @@ -108,10 +112,10 @@ struct ComposeListViewFramePreferenceKey: PreferenceKey { } extension View { - func listRow() -> some View { + func listRow(backgroundColor: Color) -> some View { self.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .leading) .listRowInsets(EdgeInsets(top: -1, leading: -1, bottom: -1, trailing: -1)) - .background(Color(.systemBackground)) + .background(backgroundColor) } } diff --git a/ShareActionExtension/Scene/View/ComposeViewModel.swift b/ShareActionExtension/Scene/View/ComposeViewModel.swift index 8d60481d..88c2b896 100644 --- a/ShareActionExtension/Scene/View/ComposeViewModel.swift +++ b/ShareActionExtension/Scene/View/ComposeViewModel.swift @@ -16,6 +16,7 @@ class ComposeViewModel: ObservableObject { @Published var authentication: MastodonAuthentication? + @Published var backgroundColor: UIColor = .clear @Published var toolbarHeight: CGFloat = 0 @Published var viewDidAppear = false diff --git a/ShareActionExtension/Scene/View/StatusEditorView.swift b/ShareActionExtension/Scene/View/StatusEditorView.swift index 79fedc00..c945874e 100644 --- a/ShareActionExtension/Scene/View/StatusEditorView.swift +++ b/ShareActionExtension/Scene/View/StatusEditorView.swift @@ -43,6 +43,7 @@ public struct StatusEditorView: UIViewRepresentable { textView.textColor = .label textView.keyboardType = keyboardType textView.delegate = context.coordinator + textView.backgroundColor = .clear textView.translatesAutoresizingMaskIntoConstraints = false let widthLayoutConstraint = textView.widthAnchor.constraint(equalToConstant: 100) From 7aaafcdc102807a32baf9c1032c134705b318ce8 Mon Sep 17 00:00:00 2001 From: CMK Date: Tue, 20 Jul 2021 19:59:09 +0800 Subject: [PATCH 07/11] fix: notification badge image color issue --- .../Section/Status/NotificationSection.swift | 28 +++++++++++++------ .../NotificationStatusTableViewCell.swift | 26 ++++++++++++----- .../TableViewCell/PickServerTitleCell.swift | 2 +- 3 files changed, 40 insertions(+), 16 deletions(-) diff --git a/Mastodon/Diffiable/Section/Status/NotificationSection.swift b/Mastodon/Diffiable/Section/Status/NotificationSection.swift index 63e88bdd..5769c912 100644 --- a/Mastodon/Diffiable/Section/Status/NotificationSection.swift +++ b/Mastodon/Diffiable/Section/Status/NotificationSection.swift @@ -44,14 +44,26 @@ extension NotificationSection { avatarImageURL: notification.account.avatarImageURL() ) ) - cell.actionImageView.image = UIImage( - systemName: notification.notificationType.actionImageName, - withConfiguration: UIImage.SymbolConfiguration( - pointSize: 12, weight: .semibold - ) - )? - .withRenderingMode(.alwaysTemplate) - .af.imageAspectScaled(toFit: CGSize(width: 14, height: 14)) + + func createActionImage() -> UIImage? { + return UIImage( + systemName: notification.notificationType.actionImageName, + withConfiguration: UIImage.SymbolConfiguration( + pointSize: 12, weight: .semibold + ) + )? + .withTintColor(.systemBackground) + .af.imageAspectScaled(toFit: CGSize(width: 14, height: 14)) + } + + cell.actionImageView.image = createActionImage() + cell.traitCollectionDidChange + .receive(on: DispatchQueue.main) + .sink { [weak cell] in + guard let cell = cell else { return } + cell.actionImageView.image = createActionImage() + } + .store(in: &cell.disposeBag) cell.actionImageView.backgroundColor = notification.notificationType.color diff --git a/Mastodon/Scene/Notification/TableViewCell/NotificationStatusTableViewCell.swift b/Mastodon/Scene/Notification/TableViewCell/NotificationStatusTableViewCell.swift index 9c3558ea..6042c0bb 100644 --- a/Mastodon/Scene/Notification/TableViewCell/NotificationStatusTableViewCell.swift +++ b/Mastodon/Scene/Notification/TableViewCell/NotificationStatusTableViewCell.swift @@ -50,17 +50,18 @@ final class NotificationStatusTableViewCell: UITableViewCell, StatusCell { let imageView = FLAnimatedImageView() return imageView }() + + + let traitCollectionDidChange = PassthroughSubject() let actionImageView: UIImageView = { let imageView = UIImageView() imageView.contentMode = .center - imageView.tintColor = Asset.Theme.Mastodon.systemBackground.color imageView.isOpaque = true imageView.layer.masksToBounds = true imageView.layer.cornerRadius = NotificationStatusTableViewCell.actionImageViewSize.width * 0.5 imageView.layer.cornerCurve = .circular imageView.layer.borderWidth = NotificationStatusTableViewCell.actionImageBorderWidth - imageView.layer.borderColor = Asset.Theme.Mastodon.systemBackground.color.cgColor imageView.layer.shouldRasterize = true imageView.layer.rasterizationScale = UIScreen.main.scale return imageView @@ -197,8 +198,8 @@ extension NotificationStatusTableViewCell { NSLayoutConstraint.activate([ actionImageView.centerYAnchor.constraint(equalTo: avatarContainer.bottomAnchor), actionImageView.centerXAnchor.constraint(equalTo: avatarContainer.trailingAnchor), - actionImageView.widthAnchor.constraint(equalToConstant: NotificationStatusTableViewCell.actionImageViewSize.width), - actionImageView.heightAnchor.constraint(equalToConstant: NotificationStatusTableViewCell.actionImageViewSize.height), + actionImageView.widthAnchor.constraint(equalToConstant: NotificationStatusTableViewCell.actionImageViewSize.width).priority(.required - 1), + actionImageView.heightAnchor.constraint(equalTo: actionImageView.widthAnchor, multiplier: 1.0), ]) containerStackView.addArrangedSubview(contentStackView) @@ -282,14 +283,23 @@ extension NotificationStatusTableViewCell { nameLabel.addGestureRecognizer(authorNameLabelTapGestureRecognizer) resetSeparatorLineLayout() + + setupBackgroundColor(theme: ThemeService.shared.currentTheme.value) + ThemeService.shared.currentTheme + .receive(on: DispatchQueue.main) + .sink { [weak self] theme in + guard let self = self else { return } + self.setupBackgroundColor(theme: theme) + } + .store(in: &disposeBag) } override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { super.traitCollectionDidChange(previousTraitCollection) resetSeparatorLineLayout() - avatarImageView.layer.borderColor = Asset.Theme.Mastodon.systemBackground.color.cgColor - statusContainerView.layer.borderColor = Asset.Colors.Border.notificationStatus.color.cgColor + setupBackgroundColor(theme: ThemeService.shared.currentTheme.value) + traitCollectionDidChange.send() } private func configure(isFiltered: Bool) { @@ -297,12 +307,14 @@ extension NotificationStatusTableViewCell { filteredLabel.isHidden = !isFiltered isUserInteractionEnabled = !isFiltered } - } extension NotificationStatusTableViewCell { private func setupBackgroundColor(theme: Theme) { + actionImageView.layer.borderColor = theme.systemBackgroundColor.cgColor + avatarImageView.layer.borderColor = Asset.Theme.Mastodon.systemBackground.color.cgColor + statusContainerView.layer.borderColor = Asset.Colors.Border.notificationStatus.color.cgColor statusContainerView.backgroundColor = UIColor(dynamicProvider: { traitCollection in return traitCollection.userInterfaceStyle == .light ? theme.systemBackgroundColor : theme.tertiarySystemGroupedBackgroundColor }) diff --git a/Mastodon/Scene/Onboarding/PickServer/TableViewCell/PickServerTitleCell.swift b/Mastodon/Scene/Onboarding/PickServer/TableViewCell/PickServerTitleCell.swift index 2cfd5ec3..682ebbf3 100644 --- a/Mastodon/Scene/Onboarding/PickServer/TableViewCell/PickServerTitleCell.swift +++ b/Mastodon/Scene/Onboarding/PickServer/TableViewCell/PickServerTitleCell.swift @@ -35,7 +35,7 @@ extension PickServerTitleCell { private func _init() { selectionStyle = .none - backgroundColor = Asset.Theme.Mastodon.systemBackground.color + backgroundColor = Asset.Theme.Mastodon.systemGroupedBackground.color contentView.addSubview(titleLabel) NSLayoutConstraint.activate([ From 4ee065eda453b58b5d2207ebb031e93d2c61a54c Mon Sep 17 00:00:00 2001 From: CMK Date: Tue, 20 Jul 2021 20:04:56 +0800 Subject: [PATCH 08/11] feat haptic feedback for reply action --- Mastodon/Protocol/StatusProvider/StatusProviderFacade.swift | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Mastodon/Protocol/StatusProvider/StatusProviderFacade.swift b/Mastodon/Protocol/StatusProvider/StatusProviderFacade.swift index 0ae8a3ca..40f0f6bb 100644 --- a/Mastodon/Protocol/StatusProvider/StatusProviderFacade.swift +++ b/Mastodon/Protocol/StatusProvider/StatusProviderFacade.swift @@ -525,7 +525,10 @@ extension StatusProviderFacade { .sink { [weak provider] status in guard let provider = provider else { return } guard let status = status?.reblog ?? status else { return } - + + let generator = UIImpactFeedbackGenerator(style: .light) + generator.impactOccurred() + let composeViewModel = ComposeViewModel(context: provider.context, composeKind: .reply(repliedToStatusObjectID: status.objectID)) provider.coordinator.present(scene: .compose(viewModel: composeViewModel), from: provider, transition: .modal(animated: true, completion: nil)) } From 16d6a835c03d30a833920cf0d6baf5e8b3fd2878 Mon Sep 17 00:00:00 2001 From: CMK Date: Tue, 20 Jul 2021 20:24:53 +0800 Subject: [PATCH 09/11] feat: adapt large content content viewer for tab bar item --- .../Scene/MainTab/MainTabBarController.swift | 17 ++++++++++++++++- .../ThemeService/ThemeService+Appearance.swift | 4 ++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/Mastodon/Scene/MainTab/MainTabBarController.swift b/Mastodon/Scene/MainTab/MainTabBarController.swift index 5a85e872..1f9bcb57 100644 --- a/Mastodon/Scene/MainTab/MainTabBarController.swift +++ b/Mastodon/Scene/MainTab/MainTabBarController.swift @@ -42,6 +42,15 @@ class MainTabBarController: UITabBarController { case .me: return UIImage(systemName: "person.fill")! } } + + var largeImage: UIImage { + switch self { + case .home: return UIImage(systemName: "house.fill", withConfiguration: UIImage.SymbolConfiguration(pointSize: 80))! + case .search: return UIImage(systemName: "magnifyingglass", withConfiguration: UIImage.SymbolConfiguration(pointSize: 80))! + case .notification: return UIImage(systemName: "bell.fill", withConfiguration: UIImage.SymbolConfiguration(pointSize: 80))! + case .me: return UIImage(systemName: "person.fill", withConfiguration: UIImage.SymbolConfiguration(pointSize: 80))! + } + } func viewController(context: AppContext, coordinator: SceneCoordinator) -> UIViewController { let viewController: UIViewController @@ -112,13 +121,19 @@ extension MainTabBarController { let tabs = Tab.allCases let viewControllers: [UIViewController] = tabs.map { tab in let viewController = tab.viewController(context: context, coordinator: coordinator) - viewController.tabBarItem.title = "" // set text to empty string for image only style (SDK failed to layout when set to nil) + viewController.tabBarItem.title = tab.title viewController.tabBarItem.image = tab.image viewController.tabBarItem.accessibilityLabel = tab.title + viewController.tabBarItem.largeContentSizeImage = tab.largeImage + viewController.tabBarItem.imageInsets = UIEdgeInsets(top: 6, left: 0, bottom: -6, right: 0) return viewController } setViewControllers(viewControllers, animated: false) selectedIndex = 0 + + UITabBarItem.appearance().setTitleTextAttributes([.foregroundColor : UIColor.clear], for: .normal) + UITabBarItem.appearance().setTitleTextAttributes([.foregroundColor : UIColor.clear], for: .highlighted) + UITabBarItem.appearance().setTitleTextAttributes([.foregroundColor : UIColor.clear], for: .selected) context.apiService.error .receive(on: DispatchQueue.main) diff --git a/Mastodon/Service/ThemeService/ThemeService+Appearance.swift b/Mastodon/Service/ThemeService/ThemeService+Appearance.swift index da14a777..182fe870 100644 --- a/Mastodon/Service/ThemeService/ThemeService+Appearance.swift +++ b/Mastodon/Service/ThemeService/ThemeService+Appearance.swift @@ -30,6 +30,10 @@ extension ThemeService { tabBarAppearance.configureWithDefaultBackground() let tabBarItemAppearance = UITabBarItemAppearance() + tabBarItemAppearance.selected.titleTextAttributes = [.foregroundColor: UIColor.clear] + tabBarItemAppearance.focused.titleTextAttributes = [.foregroundColor: UIColor.clear] + tabBarItemAppearance.normal.titleTextAttributes = [.foregroundColor: UIColor.clear] + tabBarItemAppearance.disabled.titleTextAttributes = [.foregroundColor: UIColor.clear] tabBarItemAppearance.selected.iconColor = theme.tabBarItemSelectedIconColor tabBarItemAppearance.focused.iconColor = theme.tabBarItemFocusedIconColor tabBarItemAppearance.normal.iconColor = theme.tabBarItemNormalIconColor From 41219d8a1755b251454231bfd498f9ab099ddd17 Mon Sep 17 00:00:00 2001 From: CMK Date: Tue, 20 Jul 2021 20:26:30 +0800 Subject: [PATCH 10/11] chore: set share media only --- ShareActionExtension/Info.plist | 4 ---- 1 file changed, 4 deletions(-) diff --git a/ShareActionExtension/Info.plist b/ShareActionExtension/Info.plist index 52924bee..b6faa22c 100644 --- a/ShareActionExtension/Info.plist +++ b/ShareActionExtension/Info.plist @@ -30,10 +30,6 @@ 4 NSExtensionActivationSupportsMovieWithMaxCount 1 - NSExtensionActivationSupportsText - - NSExtensionActivationSupportsWebURLWithMaxCount - 1 NSExtensionMainStoryboard From 8daf36123e89213af743c9681a5188c85e3c3109 Mon Sep 17 00:00:00 2001 From: CMK Date: Tue, 20 Jul 2021 20:34:27 +0800 Subject: [PATCH 11/11] chore: update version to 0.9.1 (41) --- Mastodon.xcodeproj/project.pbxproj | 48 +++++++++---------- .../xcschemes/xcschememanagement.plist | 6 +-- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/Mastodon.xcodeproj/project.pbxproj b/Mastodon.xcodeproj/project.pbxproj index b545bc13..51128d9e 100644 --- a/Mastodon.xcodeproj/project.pbxproj +++ b/Mastodon.xcodeproj/project.pbxproj @@ -4323,7 +4323,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Mastodon/Mastodon.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 40; + CURRENT_PROJECT_VERSION = 41; DEVELOPMENT_ASSET_PATHS = "Mastodon/Resources/Preview\\ Assets.xcassets"; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = Mastodon/Info.plist; @@ -4331,7 +4331,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 0.9.0; + MARKETING_VERSION = 0.9.1; PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -4350,7 +4350,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Mastodon/Mastodon.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 40; + CURRENT_PROJECT_VERSION = 41; DEVELOPMENT_ASSET_PATHS = "Mastodon/Resources/Preview\\ Assets.xcassets"; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = Mastodon/Info.plist; @@ -4358,7 +4358,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 0.9.0; + MARKETING_VERSION = 0.9.1; PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -4613,7 +4613,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = ShareActionExtension/ShareActionExtension.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 40; + CURRENT_PROJECT_VERSION = 41; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = ShareActionExtension/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -4621,7 +4621,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 0.9.0; + MARKETING_VERSION = 0.9.1; PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app.ShareActionExtension; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; @@ -4637,7 +4637,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = ShareActionExtension/ShareActionExtension.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 40; + CURRENT_PROJECT_VERSION = 41; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = ShareActionExtension/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -4645,7 +4645,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 0.9.0; + MARKETING_VERSION = 0.9.1; PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app.ShareActionExtension; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; @@ -4661,7 +4661,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = ShareActionExtension/ShareActionExtension.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 40; + CURRENT_PROJECT_VERSION = 41; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = ShareActionExtension/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -4669,7 +4669,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 0.9.0; + MARKETING_VERSION = 0.9.1; PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app.ShareActionExtension; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; @@ -4685,7 +4685,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = ShareActionExtension/ShareActionExtension.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 40; + CURRENT_PROJECT_VERSION = 41; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = ShareActionExtension/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -4693,7 +4693,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 0.9.0; + MARKETING_VERSION = 0.9.1; PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app.ShareActionExtension; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; @@ -4774,7 +4774,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Mastodon/Mastodon.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 40; + CURRENT_PROJECT_VERSION = 41; DEVELOPMENT_ASSET_PATHS = "Mastodon/Resources/Preview\\ Assets.xcassets"; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = Mastodon/Info.plist; @@ -4782,7 +4782,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 0.9.0; + MARKETING_VERSION = 0.9.1; PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -4888,7 +4888,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = NotificationService/NotificationService.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 40; + CURRENT_PROJECT_VERSION = 41; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = NotificationService/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -4896,7 +4896,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 0.9.0; + MARKETING_VERSION = 0.9.1; PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app.NotificationService; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; @@ -5007,7 +5007,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Mastodon/Mastodon.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 40; + CURRENT_PROJECT_VERSION = 41; DEVELOPMENT_ASSET_PATHS = "Mastodon/Resources/Preview\\ Assets.xcassets"; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = Mastodon/Info.plist; @@ -5015,7 +5015,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 0.9.0; + MARKETING_VERSION = 0.9.1; PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -5121,7 +5121,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = NotificationService/NotificationService.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 40; + CURRENT_PROJECT_VERSION = 41; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = NotificationService/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -5129,7 +5129,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 0.9.0; + MARKETING_VERSION = 0.9.1; PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app.NotificationService; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; @@ -5175,7 +5175,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = NotificationService/NotificationService.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 40; + CURRENT_PROJECT_VERSION = 41; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = NotificationService/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -5183,7 +5183,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 0.9.0; + MARKETING_VERSION = 0.9.1; PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app.NotificationService; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; @@ -5198,7 +5198,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = NotificationService/NotificationService.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 40; + CURRENT_PROJECT_VERSION = 41; DEVELOPMENT_TEAM = 5Z4GVSS33P; INFOPLIST_FILE = NotificationService/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -5206,7 +5206,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 0.9.0; + MARKETING_VERSION = 0.9.1; PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.app.NotificationService; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; diff --git a/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist b/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist index 4dc2e933..e79a0423 100644 --- a/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist @@ -12,7 +12,7 @@ CoreDataStack.xcscheme_^#shared#^_ orderHint - 23 + 24 Mastodon - ASDK.xcscheme_^#shared#^_ @@ -37,12 +37,12 @@ NotificationService.xcscheme_^#shared#^_ orderHint - 21 + 22 ShareActionExtension.xcscheme_^#shared#^_ orderHint - 22 + 21 SuppressBuildableAutocreation