From 8e749fd75b65fff7b43619ec9b7d3a12e844e20c Mon Sep 17 00:00:00 2001 From: CMK Date: Fri, 16 Jul 2021 16:21:47 +0800 Subject: [PATCH] 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 64bf9c857..2d6224e72 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 69d8176a1..f82cb5ab8 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 ecde1bcc5..4c04247e9 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 35766c0bc..7054661b6 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 ca0b3c44e..b48b01a7e 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 84a463671..caa2fbe05 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 948d22b0f..aed539047 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 47589f32c..23b5015fd 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 000000000..79896fdab --- /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 266fa6594..4d62a5c2c 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 000000000..a96291ddc --- /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 000000000..e971d0777 --- /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 000000000..ea226b261 --- /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 000000000..c0479c96c --- /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 000000000..bea00c511 --- /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) + } + } +}