diff --git a/IceCubesApp.xcodeproj/project.pbxproj b/IceCubesApp.xcodeproj/project.pbxproj index d3e8b979..3c8e5abb 100644 --- a/IceCubesApp.xcodeproj/project.pbxproj +++ b/IceCubesApp.xcodeproj/project.pbxproj @@ -43,6 +43,12 @@ 9F7335F22967608F00AFF0BA /* AddRemoteTimelineVIew.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F7335F12967608F00AFF0BA /* AddRemoteTimelineVIew.swift */; }; 9F7335F92968576500AFF0BA /* DisplaySettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F7335F82968576500AFF0BA /* DisplaySettingsView.swift */; }; 9FAD85832971BF7200496AB1 /* Secret.plist in Resources */ = {isa = PBXBuildFile; fileRef = 9FAD85822971BF7200496AB1 /* Secret.plist */; }; + 9FAD858B29743F7400496AB1 /* ShareViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FAD858A29743F7400496AB1 /* ShareViewController.swift */; }; + 9FAD858E29743F7400496AB1 /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9FAD858C29743F7400496AB1 /* MainInterface.storyboard */; }; + 9FAD859229743F7400496AB1 /* IceCubesShareExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 9FAD858829743F7400496AB1 /* IceCubesShareExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + 9FAD85982974405D00496AB1 /* Status in Frameworks */ = {isa = PBXBuildFile; productRef = 9FAD85972974405D00496AB1 /* Status */; }; + 9FAD859A297440CB00496AB1 /* KeychainSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 9FAD8599297440CB00496AB1 /* KeychainSwift */; }; + 9FAD859C2974422700496AB1 /* AppAccount in Frameworks */ = {isa = PBXBuildFile; productRef = 9FAD859B2974422700496AB1 /* AppAccount */; }; 9FAE4ACB293783B000772766 /* SettingsTab.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FAE4ACA293783B000772766 /* SettingsTab.swift */; }; 9FAE4ACE29379A5A00772766 /* KeychainSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 9FAE4ACD29379A5A00772766 /* KeychainSwift */; }; 9FBFE63D292A715500C250E9 /* IceCubesApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FBFE63C292A715500C250E9 /* IceCubesApp.swift */; }; @@ -61,6 +67,13 @@ remoteGlobalIDString = 9F2A5415296AB631009B2D7C; remoteInfo = IceCubesNotifications; }; + 9FAD859029743F7400496AB1 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 9FBFE631292A715500C250E9 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 9FAD858729743F7400496AB1; + remoteInfo = IceCubesShareExtension; + }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -71,6 +84,7 @@ dstSubfolderSpec = 13; files = ( 9F2A541D296AB631009B2D7C /* IceCubesNotifications.appex in Embed Foundation Extensions */, + 9FAD859229743F7400496AB1 /* IceCubesShareExtension.appex in Embed Foundation Extensions */, ); name = "Embed Foundation Extensions"; runOnlyForDeploymentPostprocessing = 0; @@ -114,6 +128,11 @@ 9F7335F12967608F00AFF0BA /* AddRemoteTimelineVIew.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddRemoteTimelineVIew.swift; sourceTree = ""; }; 9F7335F82968576500AFF0BA /* DisplaySettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisplaySettingsView.swift; sourceTree = ""; }; 9FAD85822971BF7200496AB1 /* Secret.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Secret.plist; sourceTree = ""; }; + 9FAD858829743F7400496AB1 /* IceCubesShareExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = IceCubesShareExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; + 9FAD858A29743F7400496AB1 /* ShareViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareViewController.swift; sourceTree = ""; }; + 9FAD858D29743F7400496AB1 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/MainInterface.storyboard; sourceTree = ""; }; + 9FAD858F29743F7400496AB1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 9FAD859629743F7E00496AB1 /* IceCubesShareExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = IceCubesShareExtension.entitlements; sourceTree = ""; }; 9FAE4AC8293774FF00772766 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; 9FAE4ACA293783B000772766 /* SettingsTab.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsTab.swift; sourceTree = ""; }; 9FBFE639292A715500C250E9 /* IceCubesApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = IceCubesApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -136,6 +155,16 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 9FAD858529743F7400496AB1 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 9FAD859C2974422700496AB1 /* AppAccount in Frameworks */, + 9FAD859A297440CB00496AB1 /* KeychainSwift in Frameworks */, + 9FAD85982974405D00496AB1 /* Status in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 9FBFE636292A715500C250E9 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -204,6 +233,17 @@ path = Timeline; sourceTree = ""; }; + 9FAD858929743F7400496AB1 /* IceCubesShareExtension */ = { + isa = PBXGroup; + children = ( + 9FAD859629743F7E00496AB1 /* IceCubesShareExtension.entitlements */, + 9FAD858A29743F7400496AB1 /* ShareViewController.swift */, + 9FAD858C29743F7400496AB1 /* MainInterface.storyboard */, + 9FAD858F29743F7400496AB1 /* Info.plist */, + ); + path = IceCubesShareExtension; + sourceTree = ""; + }; 9FAE4AC9293783A200772766 /* Tabs */ = { isa = PBXGroup; children = ( @@ -222,6 +262,7 @@ children = ( 9FBFE63B292A715500C250E9 /* IceCubesApp */, 9F2A5417296AB631009B2D7C /* IceCubesNotifications */, + 9FAD858929743F7400496AB1 /* IceCubesShareExtension */, 9FBFE63A292A715500C250E9 /* Products */, 9FBFE64C292A72BD00C250E9 /* Frameworks */, 9FE3DB55296FEF5800628CB0 /* AppAccount */, @@ -244,6 +285,7 @@ children = ( 9FBFE639292A715500C250E9 /* IceCubesApp.app */, 9F2A5416296AB631009B2D7C /* IceCubesNotifications.appex */, + 9FAD858829743F7400496AB1 /* IceCubesShareExtension.appex */, ); name = Products; sourceTree = ""; @@ -311,6 +353,28 @@ productReference = 9F2A5416296AB631009B2D7C /* IceCubesNotifications.appex */; productType = "com.apple.product-type.app-extension"; }; + 9FAD858729743F7400496AB1 /* IceCubesShareExtension */ = { + isa = PBXNativeTarget; + buildConfigurationList = 9FAD859329743F7400496AB1 /* Build configuration list for PBXNativeTarget "IceCubesShareExtension" */; + buildPhases = ( + 9FAD858429743F7400496AB1 /* Sources */, + 9FAD858529743F7400496AB1 /* Frameworks */, + 9FAD858629743F7400496AB1 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = IceCubesShareExtension; + packageProductDependencies = ( + 9FAD85972974405D00496AB1 /* Status */, + 9FAD8599297440CB00496AB1 /* KeychainSwift */, + 9FAD859B2974422700496AB1 /* AppAccount */, + ); + productName = IceCubesShareExtension; + productReference = 9FAD858829743F7400496AB1 /* IceCubesShareExtension.appex */; + productType = "com.apple.product-type.app-extension"; + }; 9FBFE638292A715500C250E9 /* IceCubesApp */ = { isa = PBXNativeTarget; buildConfigurationList = 9FBFE648292A715600C250E9 /* Build configuration list for PBXNativeTarget "IceCubesApp" */; @@ -324,6 +388,7 @@ ); dependencies = ( 9F2A541C296AB631009B2D7C /* PBXTargetDependency */, + 9FAD859129743F7400496AB1 /* PBXTargetDependency */, ); name = IceCubesApp; packageProductDependencies = ( @@ -359,6 +424,9 @@ 9F2A5415296AB631009B2D7C = { CreatedOnToolsVersion = 14.2; }; + 9FAD858729743F7400496AB1 = { + CreatedOnToolsVersion = 14.2; + }; 9FBFE638292A715500C250E9 = { CreatedOnToolsVersion = 14.1; }; @@ -383,6 +451,7 @@ targets = ( 9FBFE638292A715500C250E9 /* IceCubesApp */, 9F2A5415296AB631009B2D7C /* IceCubesNotifications */, + 9FAD858729743F7400496AB1 /* IceCubesShareExtension */, ); }; /* End PBXProject section */ @@ -395,6 +464,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 9FAD858629743F7400496AB1 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 9FAD858E29743F7400496AB1 /* MainInterface.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 9FBFE637292A715500C250E9 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -419,6 +496,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 9FAD858429743F7400496AB1 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 9FAD858B29743F7400496AB1 /* ShareViewController.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 9FBFE635292A715500C250E9 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -450,8 +535,24 @@ target = 9F2A5415296AB631009B2D7C /* IceCubesNotifications */; targetProxy = 9F2A541B296AB631009B2D7C /* PBXContainerItemProxy */; }; + 9FAD859129743F7400496AB1 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 9FAD858729743F7400496AB1 /* IceCubesShareExtension */; + targetProxy = 9FAD859029743F7400496AB1 /* PBXContainerItemProxy */; + }; /* End PBXTargetDependency section */ +/* Begin PBXVariantGroup section */ + 9FAD858C29743F7400496AB1 /* MainInterface.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 9FAD858D29743F7400496AB1 /* Base */, + ); + name = MainInterface.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + /* Begin XCBuildConfiguration section */ 9F2A541F296AB631009B2D7C /* Debug */ = { isa = XCBuildConfiguration; @@ -514,6 +615,67 @@ }; name = Release; }; + 9FAD859429743F7400496AB1 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_ENTITLEMENTS = IceCubesShareExtension/IceCubesShareExtension.entitlements; + CODE_SIGN_IDENTITY = "-"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = Z6P74P6T99; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = IceCubesShareExtension/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = IceCubesShareExtension; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + IPHONEOS_DEPLOYMENT_TARGET = 16.1; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.thomasricouard.IceCubesApp.IceCubesShareExtension; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 9FAD859529743F7400496AB1 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_ENTITLEMENTS = IceCubesShareExtension/IceCubesShareExtension.entitlements; + CODE_SIGN_IDENTITY = "-"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = Z6P74P6T99; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = IceCubesShareExtension/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = IceCubesShareExtension; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + IPHONEOS_DEPLOYMENT_TARGET = 16.1; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.thomasricouard.IceCubesApp.IceCubesShareExtension; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; 9FBFE646292A715600C250E9 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -739,6 +901,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + 9FAD859329743F7400496AB1 /* Build configuration list for PBXNativeTarget "IceCubesShareExtension" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 9FAD859429743F7400496AB1 /* Debug */, + 9FAD859529743F7400496AB1 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 9FBFE634292A715500C250E9 /* Build configuration list for PBXProject "IceCubesApp" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -834,6 +1005,19 @@ isa = XCSwiftPackageProductDependency; productName = Conversations; }; + 9FAD85972974405D00496AB1 /* Status */ = { + isa = XCSwiftPackageProductDependency; + productName = Status; + }; + 9FAD8599297440CB00496AB1 /* KeychainSwift */ = { + isa = XCSwiftPackageProductDependency; + package = 9FAE4ACC29379A5A00772766 /* XCRemoteSwiftPackageReference "keychain-swift" */; + productName = KeychainSwift; + }; + 9FAD859B2974422700496AB1 /* AppAccount */ = { + isa = XCSwiftPackageProductDependency; + productName = AppAccount; + }; 9FAE4ACD29379A5A00772766 /* KeychainSwift */ = { isa = XCSwiftPackageProductDependency; package = 9FAE4ACC29379A5A00772766 /* XCRemoteSwiftPackageReference "keychain-swift" */; diff --git a/IceCubesShareExtension/Base.lproj/MainInterface.storyboard b/IceCubesShareExtension/Base.lproj/MainInterface.storyboard new file mode 100644 index 00000000..76ae5e7d --- /dev/null +++ b/IceCubesShareExtension/Base.lproj/MainInterface.storyboard @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/IceCubesShareExtension/IceCubesShareExtension.entitlements b/IceCubesShareExtension/IceCubesShareExtension.entitlements new file mode 100644 index 00000000..8ffdc6f3 --- /dev/null +++ b/IceCubesShareExtension/IceCubesShareExtension.entitlements @@ -0,0 +1,14 @@ + + + + + com.apple.security.application-groups + + group.icecubesapps + + keychain-access-groups + + $(AppIdentifierPrefix)com.thomasricouard.IceCubesApp + + + diff --git a/IceCubesShareExtension/Info.plist b/IceCubesShareExtension/Info.plist new file mode 100644 index 00000000..4b1f7e70 --- /dev/null +++ b/IceCubesShareExtension/Info.plist @@ -0,0 +1,18 @@ + + + + + NSExtension + + NSExtensionAttributes + + NSExtensionActivationRule + TRUEPREDICATE + + NSExtensionMainStoryboard + MainInterface + NSExtensionPointIdentifier + com.apple.share-services + + + diff --git a/IceCubesShareExtension/ShareViewController.swift b/IceCubesShareExtension/ShareViewController.swift new file mode 100644 index 00000000..cb9f0c1d --- /dev/null +++ b/IceCubesShareExtension/ShareViewController.swift @@ -0,0 +1,57 @@ +import SwiftUI +import UIKit +import Status +import DesignSystem +import Account +import Network +import Env +import AppAccount + +class ShareViewController: UIViewController { + @IBOutlet var container: UIView! + + override func viewDidLoad() { + super.viewDidLoad() + + let client = AppAccountsManager.shared.currentClient + let account = CurrentAccount() + let instance = CurrentInstance() + account.setClient(client: client) + instance.setClient(client: client) + let theme = Theme() + + overrideUserInterfaceStyle = theme.selectedScheme == .dark ? .dark : .light + + if let item = extensionContext?.inputItems.first as? NSExtensionItem { + if let attachments = item.attachments { + let view = StatusEditorView(mode: .shareExtension(items: attachments)) + .environmentObject(UserPreferences()) + .environmentObject(client) + .environmentObject(account) + .environmentObject(theme) + .environmentObject(instance) + .tint(theme.tintColor) + .preferredColorScheme(theme.selectedScheme == .dark ? .dark : .light) + let childView = UIHostingController(rootView: view) + self.addChild(childView) + childView.view.frame = self.container.bounds + self.container.addSubview(childView.view) + childView.didMove(toParent: self) + } + } + + NotificationCenter.default.addObserver(forName: NotificationsName.shareSheetClose, + object: nil, + queue: nil) { _ in + self.close() + } + } + + func close() { + extensionContext?.completeRequest(returningItems: [], completionHandler: nil) + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + } +} diff --git a/Packages/Env/Sources/Env/NotificationsName.swift b/Packages/Env/Sources/Env/NotificationsName.swift new file mode 100644 index 00000000..2cf24be5 --- /dev/null +++ b/Packages/Env/Sources/Env/NotificationsName.swift @@ -0,0 +1,5 @@ +import UIKit + +public enum NotificationsName { + public static let shareSheetClose = NSNotification.Name("shareSheetClose") +} diff --git a/Packages/Status/Sources/Status/Editor/Components/StatusEditorAccessoryView.swift b/Packages/Status/Sources/Status/Editor/Components/StatusEditorAccessoryView.swift index cefd8de3..31b90d19 100644 --- a/Packages/Status/Sources/Status/Editor/Components/StatusEditorAccessoryView.swift +++ b/Packages/Status/Sources/Status/Editor/Components/StatusEditorAccessoryView.swift @@ -55,13 +55,15 @@ struct StatusEditorAccessoryView: View { Image(systemName: viewModel.spoilerOn ? "exclamationmark.triangle.fill": "exclamationmark.triangle") } - Button { - isDrafsSheetDisplayed = true - } label: { - Image(systemName: "archivebox") + if !viewModel.mode.isInShareExtension { + Button { + isDrafsSheetDisplayed = true + } label: { + Image(systemName: "archivebox") + } + } - - + Spacer() characterCountView diff --git a/Packages/Status/Sources/Status/Editor/Components/StatusEditorMediaView.swift b/Packages/Status/Sources/Status/Editor/Components/StatusEditorMediaView.swift index 67ca5af2..630983a3 100644 --- a/Packages/Status/Sources/Status/Editor/Components/StatusEditorMediaView.swift +++ b/Packages/Status/Sources/Status/Editor/Components/StatusEditorMediaView.swift @@ -5,6 +5,7 @@ import DesignSystem import NukeUI struct StatusEditorMediaView: View { + @EnvironmentObject private var theme: Theme @ObservedObject var viewModel: StatusEditorViewModel @State private var editingContainer: StatusEditorViewModel.ImageContainer? @@ -12,17 +13,17 @@ struct StatusEditorMediaView: View { ScrollView(.horizontal, showsIndicators: false) { HStack(spacing: 8) { ForEach(viewModel.mediasImages) { container in - if container.image != nil { - makeLocalImage(container: container) - } else if let url = container.mediaAttachement?.url { - Menu { - makeImageMenu(container: container) - } label: { - ZStack(alignment: .bottomTrailing) { + Menu { + makeImageMenu(container: container) + } label: { + ZStack(alignment: .bottomTrailing) { + if container.image != nil { + makeLocalImage(container: container) + } else if let url = container.mediaAttachement?.url { makeLazyImage(url: url) - if container.mediaAttachement?.description?.isEmpty == false { - altMarker - } + } + if container.mediaAttachement?.description?.isEmpty == false { + altMarker } } } @@ -32,6 +33,7 @@ struct StatusEditorMediaView: View { } .sheet(item: $editingContainer) { container in StatusEditorMediaEditView(viewModel: viewModel, container: container) + .preferredColorScheme(theme.selectedScheme == .dark ? .dark : .light) } } @@ -39,7 +41,7 @@ struct StatusEditorMediaView: View { ZStack(alignment: .center) { Image(uiImage: container.image!) .resizable() - .blur(radius: 20 ) + .blur(radius: container.mediaAttachement == nil ? 20 : 0) .aspectRatio(contentMode: .fill) .frame(width: 150, height: 150) .cornerRadius(8) @@ -67,7 +69,7 @@ struct StatusEditorMediaView: View { } .buttonStyle(.bordered) } - } else { + } else if container.mediaAttachement == nil{ ProgressView() } } diff --git a/Packages/Status/Sources/Status/Editor/Components/StatusEditorUTTypeSupported.swift b/Packages/Status/Sources/Status/Editor/Components/StatusEditorUTTypeSupported.swift new file mode 100644 index 00000000..11177a80 --- /dev/null +++ b/Packages/Status/Sources/Status/Editor/Components/StatusEditorUTTypeSupported.swift @@ -0,0 +1,30 @@ +import UIKit +import Foundation + +@MainActor +enum StatusEditorUTTypeSupported: String, CaseIterable { + case url = "public.url" + case text = "public.text" + case image = "public.image" + case jpeg = "public.jpeg" + case png = "public.png" + + func loadItemContent(item: NSItemProvider) async throws -> Any? { + let result = try await item.loadItem(forTypeIdentifier: rawValue) + if self == .jpeg || self == .png, + let imageURL = result as? URL, + let data = try? Data(contentsOf: imageURL), + let image = UIImage(data: data) { + return image + } + if let url = result as? URL { + return url.absoluteString + } else if let text = result as? String { + return text + } else if let image = result as? UIImage { + return image + } else { + return nil + } + } +} diff --git a/Packages/Status/Sources/Status/Editor/StatusEditorView.swift b/Packages/Status/Sources/Status/Editor/StatusEditorView.swift index 7229689d..c8a07db4 100644 --- a/Packages/Status/Sources/Status/Editor/StatusEditorView.swift +++ b/Packages/Status/Sources/Status/Editor/StatusEditorView.swift @@ -8,6 +8,7 @@ import Network import PhotosUI import NukeUI import EmojiText +import UIKit public struct StatusEditorView: View { @EnvironmentObject private var preferences: UserPreferences @@ -72,6 +73,8 @@ public struct StatusEditorView: View { viewModel.prepareStatusText() if !client.isAuth { dismiss() + NotificationCenter.default.post(name: NotificationsName.shareSheetClose, + object: nil) } } .background(theme.primaryBackgroundColor) @@ -88,6 +91,8 @@ public struct StatusEditorView: View { let status = await viewModel.postStatus() if status != nil { dismiss() + NotificationCenter.default.post(name: NotificationsName.shareSheetClose, + object: nil) } } } label: { @@ -101,10 +106,12 @@ public struct StatusEditorView: View { } ToolbarItem(placement: .navigationBarLeading) { Button { - if !viewModel.statusText.string.isEmpty { + if !viewModel.statusText.string.isEmpty && !viewModel.mode.isInShareExtension { isDismissAlertPresented = true } else { dismiss() + NotificationCenter.default.post(name: NotificationsName.shareSheetClose, + object: nil) } } label: { Text("Cancel") @@ -117,10 +124,14 @@ public struct StatusEditorView: View { actions: { Button("Delete Draft", role: .destructive) { dismiss() + NotificationCenter.default.post(name: NotificationsName.shareSheetClose, + object: nil) } Button("Save Draft") { preferences.draftsPosts.insert(viewModel.statusText.string, at: 0) dismiss() + NotificationCenter.default.post(name: NotificationsName.shareSheetClose, + object: nil) } Button("Cancel", role: .cancel) { } }) diff --git a/Packages/Status/Sources/Status/Editor/StatusEditorViewModel.swift b/Packages/Status/Sources/Status/Editor/StatusEditorViewModel.swift index 661ec473..59ea1cf1 100644 --- a/Packages/Status/Sources/Status/Editor/StatusEditorViewModel.swift +++ b/Packages/Status/Sources/Status/Editor/StatusEditorViewModel.swift @@ -118,7 +118,7 @@ public class StatusEditorViewModel: ObservableObject { mediaIds: mediasImages.compactMap{ $0.mediaAttachement?.id }, poll: pollData) switch mode { - case .new, .replyTo, .quote, .mention: + case .new, .replyTo, .quote, .mention, .shareExtension: postStatus = try await client.post(endpoint: Statuses.postStatus(json: data)) case let .edit(status): postStatus = try await client.put(endpoint: Statuses.editStatus(id: status.id, json: data)) @@ -137,6 +137,9 @@ public class StatusEditorViewModel: ObservableObject { switch mode { case let .new(visibility): self.visibility = visibility + case let .shareExtension(items): + self.visibility = .pub + self.processItemsProvider(items: items) case let .replyTo(status): var mentionString = "" if (status.reblog?.account.acct ?? status.account.acct) != currentAccount?.acct { @@ -241,6 +244,32 @@ public class StatusEditorViewModel: ObservableObject { } } + + private func processItemsProvider(items: [NSItemProvider]) { + Task { + var initalText: String = "" + for item in items { + if let identifiter = item.registeredTypeIdentifiers.first, + let handledItemType = StatusEditorUTTypeSupported(rawValue: identifiter) { + do { + let content = try await handledItemType.loadItemContent(item: item) + if let text = content as? String { + initalText += "\(text) " + } else if let image = content as? UIImage { + mediasImages.append(.init(image: image, mediaAttachement: nil, error: nil)) + } + } catch { } + } + } + if !initalText.isEmpty { + statusText = .init(string: initalText) + selectedRange = .init(location: statusText.string.utf16.count, length: 0) + } + if !mediasImages.isEmpty { + processMediasToUpload() + } + } + } func resetPollDefaults() { pollOptions = ["", ""] @@ -373,7 +402,9 @@ public class StatusEditorViewModel: ObservableObject { if let data = originalContainer.image?.jpegData(compressionQuality: 0.90) { let uploadedMedia = try await uploadMedia(data: data) if let index = indexOf(container: newContainer) { - mediasImages[index] = .init(image: nil, mediaAttachement: uploadedMedia, error: nil) + mediasImages[index] = .init(image: mode.isInShareExtension ? originalContainer.image : nil, + mediaAttachement: uploadedMedia, + error: nil) } } } catch { diff --git a/Packages/Status/Sources/Status/Editor/StatusEditorViewModelMode.swift b/Packages/Status/Sources/Status/Editor/StatusEditorViewModelMode.swift index 49482e4b..ce74fbde 100644 --- a/Packages/Status/Sources/Status/Editor/StatusEditorViewModelMode.swift +++ b/Packages/Status/Sources/Status/Editor/StatusEditorViewModelMode.swift @@ -1,4 +1,5 @@ import Models +import UIKit extension StatusEditorViewModel { public enum Mode { @@ -7,6 +8,16 @@ extension StatusEditorViewModel { case edit(status: Status) case quote(status: Status) case mention(account: Account, visibility: Visibility) + case shareExtension(items: [NSItemProvider]) + + var isInShareExtension: Bool { + switch self { + case .shareExtension: + return true + default: + return false + } + } var isEditing: Bool { switch self { @@ -28,7 +39,7 @@ extension StatusEditorViewModel { var title: String { switch self { - case .new, .mention: + case .new, .mention, .shareExtension: return "New Post" case .edit: return "Editing your post"