diff --git a/Localizations/Localizable.strings b/Localizations/Localizable.strings index b153e48..b7f7952 100644 --- a/Localizations/Localizable.strings +++ b/Localizations/Localizable.strings @@ -35,6 +35,7 @@ "attachment.media-hidden" = "Media hidden"; "bookmarks" = "Bookmarks"; "cancel" = "Cancel"; +"error" = "Error"; "favorites" = "Favorites"; "registration.review-terms-of-use-and-privacy-policy-%@" = "Please review %@'s Terms of Use and Privacy Policy to continue"; "registration.username" = "Username"; @@ -134,6 +135,7 @@ "report.target-%@" = "Reporting %@"; "report.forward.hint" = "The account is from another server. Send an anonymized copy of the report there as well?"; "report.forward-%@" = "Forward report to %@"; +"share-extension-error.no-account-found" = "No account found"; "status.bookmark" = "Bookmark"; "status.reblogged-by" = "%@ boosted"; "status.pinned-post" = "Pinned post"; diff --git a/Metatext.xcodeproj/project.pbxproj b/Metatext.xcodeproj/project.pbxproj index 82238ba..d55d53e 100644 --- a/Metatext.xcodeproj/project.pbxproj +++ b/Metatext.xcodeproj/project.pbxproj @@ -41,6 +41,15 @@ D08B8D8D2544E6EC00B1EBEF /* PollResultView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08B8D8C2544E6EC00B1EBEF /* PollResultView.swift */; }; D08E512125786A6600FA2C5F /* UIButton+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08E512025786A6600FA2C5F /* UIButton+Extensions.swift */; }; D08E52612579D2E100FA2C5F /* DomainBlocksView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08E52602579D2E100FA2C5F /* DomainBlocksView.swift */; }; + D08E5276257C36CA00FA2C5F /* Share Extension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = D08E526C257C36CA00FA2C5F /* Share Extension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + D08E5292257C53B600FA2C5F /* NewStatusViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08E5291257C53B600FA2C5F /* NewStatusViewController.swift */; }; + D08E529C257C58D600FA2C5F /* NewStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08E529B257C58D600FA2C5F /* NewStatusView.swift */; }; + D08E52A6257C61C000FA2C5F /* ShareExtensionNavigationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08E52A5257C61C000FA2C5F /* ShareExtensionNavigationViewController.swift */; }; + D08E52B8257C62D500FA2C5F /* ViewModels in Frameworks */ = {isa = PBXBuildFile; productRef = D08E52B7257C62D500FA2C5F /* ViewModels */; }; + D08E52BD257C635800FA2C5F /* NewStatusViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08E5291257C53B600FA2C5F /* NewStatusViewController.swift */; }; + D08E52C7257C7AEE00FA2C5F /* ShareErrorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08E52C6257C7AEE00FA2C5F /* ShareErrorViewController.swift */; }; + D08E52CC257C80E300FA2C5F /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = D0C7D45724F76169001EBDBB /* Localizable.strings */; }; + D08E52D2257C811200FA2C5F /* ShareExtensionError.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08E52D1257C811200FA2C5F /* ShareExtensionError.swift */; }; D0A1F4F7252E7D4B004435BF /* TableViewDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A1F4F6252E7D4B004435BF /* TableViewDataSource.swift */; }; D0A7AC7325748BFF00E4E8AB /* ReportStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A7AC7225748BFF00E4E8AB /* ReportStatusView.swift */; }; D0B32F50250B373600311912 /* RegistrationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B32F4F250B373600311912 /* RegistrationView.swift */; }; @@ -100,6 +109,13 @@ remoteGlobalIDString = D047FA8B24C3E21200AF17C5; remoteInfo = "Metatext (iOS)"; }; + D08E5274257C36CA00FA2C5F /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = D047FA8024C3E21000AF17C5 /* Project object */; + proxyType = 1; + remoteGlobalIDString = D08E526B257C36CA00FA2C5F; + remoteInfo = "Share Extension"; + }; D0E5361E24E3EB4D00FB1CE1 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = D047FA8024C3E21000AF17C5 /* Project object */; @@ -117,6 +133,7 @@ dstSubfolderSpec = 13; files = ( D0E5362024E3EB4D00FB1CE1 /* Notification Service Extension.appex in Embed App Extensions */, + D08E5276257C36CA00FA2C5F /* Share Extension.appex in Embed App Extensions */, ); name = "Embed App Extensions"; runOnlyForDeploymentPostprocessing = 0; @@ -161,6 +178,14 @@ D08B8D8C2544E6EC00B1EBEF /* PollResultView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollResultView.swift; sourceTree = ""; }; D08E512025786A6600FA2C5F /* UIButton+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIButton+Extensions.swift"; sourceTree = ""; }; D08E52602579D2E100FA2C5F /* DomainBlocksView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomainBlocksView.swift; sourceTree = ""; }; + D08E526C257C36CA00FA2C5F /* Share Extension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "Share Extension.appex"; sourceTree = BUILT_PRODUCTS_DIR; }; + D08E5273257C36CA00FA2C5F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + D08E5277257C36CB00FA2C5F /* Share Extension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "Share Extension.entitlements"; sourceTree = ""; }; + D08E5291257C53B600FA2C5F /* NewStatusViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewStatusViewController.swift; sourceTree = ""; }; + D08E529B257C58D600FA2C5F /* NewStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewStatusView.swift; sourceTree = ""; }; + D08E52A5257C61C000FA2C5F /* ShareExtensionNavigationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareExtensionNavigationViewController.swift; sourceTree = ""; }; + D08E52C6257C7AEE00FA2C5F /* ShareErrorViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareErrorViewController.swift; sourceTree = ""; }; + D08E52D1257C811200FA2C5F /* ShareExtensionError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareExtensionError.swift; sourceTree = ""; }; D0A1F4F6252E7D4B004435BF /* TableViewDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableViewDataSource.swift; sourceTree = ""; }; D0A7AC7225748BFF00E4E8AB /* ReportStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportStatusView.swift; sourceTree = ""; }; D0AD03552505814D0085A466 /* Base16 */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Base16; sourceTree = ""; }; @@ -239,6 +264,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + D08E5269257C36CA00FA2C5F /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + D08E52B8257C62D500FA2C5F /* ViewModels in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; D0E5361624E3EB4D00FB1CE1 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -271,6 +304,7 @@ D047FA8D24C3E21200AF17C5 /* Products */, D0BECB962501BCE0002C1B13 /* Secrets */, D0BDF66524FD7A6400C7FA1C /* ServiceLayer */, + D08E526D257C36CA00FA2C5F /* Share Extension */, D0C7D41D24F76169001EBDBB /* Supporting Files */, D0C7D45324F76169001EBDBB /* System */, D0666A2224C677B400F3F04B /* Tests */, @@ -287,6 +321,7 @@ D047FA8C24C3E21200AF17C5 /* Metatext.app */, D0666A2124C677B400F3F04B /* Tests.xctest */, D0E5361924E3EB4D00FB1CE1 /* Notification Service Extension.appex */, + D08E526C257C36CA00FA2C5F /* Share Extension.appex */, ); name = Products; sourceTree = ""; @@ -332,6 +367,18 @@ path = Transitions; sourceTree = ""; }; + D08E526D257C36CA00FA2C5F /* Share Extension */ = { + isa = PBXGroup; + children = ( + D08E5273257C36CA00FA2C5F /* Info.plist */, + D08E5277257C36CB00FA2C5F /* Share Extension.entitlements */, + D08E52C6257C7AEE00FA2C5F /* ShareErrorViewController.swift */, + D08E52D1257C811200FA2C5F /* ShareExtensionError.swift */, + D08E52A5257C61C000FA2C5F /* ShareExtensionNavigationViewController.swift */, + ); + path = "Share Extension"; + sourceTree = ""; + }; D0A1F4F5252E7D2A004435BF /* Data Sources */ = { isa = PBXGroup; children = ( @@ -373,6 +420,7 @@ D0E569DF252931B100FA1D72 /* LoadMoreContentConfiguration.swift */, D0E569DA2529319100FA1D72 /* LoadMoreView.swift */, D03B1B29253818F3008F964B /* MediaPreferencesView.swift */, + D08E529B257C58D600FA2C5F /* NewStatusView.swift */, D036AA0B254B612B009094DF /* NotificationContentConfiguration.swift */, D036AA01254B6101009094DF /* NotificationListCell.swift */, D0C7D42D24F76169001EBDBB /* NotificationTypesPreferencesView.swift */, @@ -402,9 +450,10 @@ D0C7D43024F76169001EBDBB /* View Controllers */ = { isa = PBXGroup; children = ( - D08B8D41253F92B600B1EBEF /* ImagePageViewController.swift */, D08B8D49253FC36500B1EBEF /* ImageNavigationController.swift */, + D08B8D41253F92B600B1EBEF /* ImagePageViewController.swift */, D08B8D3C253F929E00B1EBEF /* ImageViewController.swift */, + D08E5291257C53B600FA2C5F /* NewStatusViewController.swift */, D06BC5E525202AD90079541D /* ProfileViewController.swift */, D0F0B12D251A97E400942152 /* TableViewController.swift */, ); @@ -481,6 +530,7 @@ ); dependencies = ( D0E5361F24E3EB4D00FB1CE1 /* PBXTargetDependency */, + D08E5275257C36CA00FA2C5F /* PBXTargetDependency */, ); name = Metatext; packageProductDependencies = ( @@ -512,6 +562,26 @@ productReference = D0666A2124C677B400F3F04B /* Tests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; + D08E526B257C36CA00FA2C5F /* Share Extension */ = { + isa = PBXNativeTarget; + buildConfigurationList = D08E5278257C36CB00FA2C5F /* Build configuration list for PBXNativeTarget "Share Extension" */; + buildPhases = ( + D08E5268257C36CA00FA2C5F /* Sources */, + D08E5269257C36CA00FA2C5F /* Frameworks */, + D08E526A257C36CA00FA2C5F /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "Share Extension"; + packageProductDependencies = ( + D08E52B7257C62D500FA2C5F /* ViewModels */, + ); + productName = "Share Extension"; + productReference = D08E526C257C36CA00FA2C5F /* Share Extension.appex */; + productType = "com.apple.product-type.app-extension"; + }; D0E5361824E3EB4D00FB1CE1 /* Notification Service Extension */ = { isa = PBXNativeTarget; buildConfigurationList = D0E5362124E3EB4D00FB1CE1 /* Build configuration list for PBXNativeTarget "Notification Service Extension" */; @@ -539,7 +609,7 @@ D047FA8024C3E21000AF17C5 /* Project object */ = { isa = PBXProject; attributes = { - LastSwiftUpdateCheck = 1200; + LastSwiftUpdateCheck = 1220; LastUpgradeCheck = 1200; ORGANIZATIONNAME = Metabolist; TargetAttributes = { @@ -551,6 +621,9 @@ LastSwiftMigration = 1200; TestTargetID = D047FA8B24C3E21200AF17C5; }; + D08E526B257C36CA00FA2C5F = { + CreatedOnToolsVersion = 12.2; + }; D0E5361824E3EB4D00FB1CE1 = { CreatedOnToolsVersion = 12.0; }; @@ -575,6 +648,7 @@ D047FA8B24C3E21200AF17C5 /* Metatext */, D0666A2024C677B400F3F04B /* Tests */, D0E5361824E3EB4D00FB1CE1 /* Notification Service Extension */, + D08E526B257C36CA00FA2C5F /* Share Extension */, ); }; /* End PBXProject section */ @@ -597,6 +671,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + D08E526A257C36CA00FA2C5F /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D08E52CC257C80E300FA2C5F /* Localizable.strings in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; D0E5361724E3EB4D00FB1CE1 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -648,6 +730,7 @@ D0BEB1F324F8EE8C001B0F04 /* StatusAttachmentView.swift in Sources */, D0C7D49A24F7616A001EBDBB /* TableView.swift in Sources */, D08B8D622540DE3B00B1EBEF /* ZoomTransitionController.swift in Sources */, + D08E529C257C58D600FA2C5F /* NewStatusView.swift in Sources */, D0F0B12E251A97E400942152 /* TableViewController.swift in Sources */, D0DD50CB256B1F24004A04F7 /* ReportView.swift in Sources */, D0FE1C8F253686F9003EF1EB /* PlayerView.swift in Sources */, @@ -662,6 +745,7 @@ D0B5FE9B251583DB00478838 /* ProfileCollection+Extensions.swift in Sources */, D0C7D49E24F7616A001EBDBB /* SecondaryNavigationView.swift in Sources */, D08B8D602540DE3B00B1EBEF /* ZoomAnimator.swift in Sources */, + D08E5292257C53B600FA2C5F /* NewStatusViewController.swift in Sources */, D08B8D672540DEB200B1EBEF /* ZoomAnimatableView.swift in Sources */, D08B8D822544D80000B1EBEF /* PollOptionButton.swift in Sources */, D0C7D4DA24F7616A001EBDBB /* View+Extensions.swift in Sources */, @@ -714,6 +798,17 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + D08E5268257C36CA00FA2C5F /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D08E52A6257C61C000FA2C5F /* ShareExtensionNavigationViewController.swift in Sources */, + D08E52BD257C635800FA2C5F /* NewStatusViewController.swift in Sources */, + D08E52D2257C811200FA2C5F /* ShareExtensionError.swift in Sources */, + D08E52C7257C7AEE00FA2C5F /* ShareErrorViewController.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; D0E5361524E3EB4D00FB1CE1 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -730,6 +825,11 @@ target = D047FA8B24C3E21200AF17C5 /* Metatext */; targetProxy = D0666A2624C677B400F3F04B /* PBXContainerItemProxy */; }; + D08E5275257C36CA00FA2C5F /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = D08E526B257C36CA00FA2C5F /* Share Extension */; + targetProxy = D08E5274257C36CA00FA2C5F /* PBXContainerItemProxy */; + }; D0E5361F24E3EB4D00FB1CE1 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = D0E5361824E3EB4D00FB1CE1 /* Notification Service Extension */; @@ -952,6 +1052,55 @@ }; name = Release; }; + D08E5279257C36CB00FA2C5F /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_ENTITLEMENTS = "Share Extension/Share Extension.entitlements"; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 82HL67AXQ2; + INFOPLIST_FILE = "Share Extension/Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 14.2; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + MARKETING_VERSION = 0.1.0; + PRODUCT_BUNDLE_IDENTIFIER = "com.metabolist.metatext.share-extension"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SUPPORTS_MACCATALYST = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + D08E527A257C36CB00FA2C5F /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_ENTITLEMENTS = "Share Extension/Share Extension.entitlements"; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 82HL67AXQ2; + INFOPLIST_FILE = "Share Extension/Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 14.2; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + MARKETING_VERSION = 0.1.0; + PRODUCT_BUNDLE_IDENTIFIER = "com.metabolist.metatext.share-extension"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SUPPORTS_MACCATALYST = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; D0E5362224E3EB4D00FB1CE1 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -1031,6 +1180,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + D08E5278257C36CB00FA2C5F /* Build configuration list for PBXNativeTarget "Share Extension" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + D08E5279257C36CB00FA2C5F /* Debug */, + D08E527A257C36CB00FA2C5F /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; D0E5362124E3EB4D00FB1CE1 /* Build configuration list for PBXNativeTarget "Notification Service Extension" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -1059,6 +1217,10 @@ package = D06B492124D4611300642749 /* XCRemoteSwiftPackageReference "Kingfisher" */; productName = KingfisherSwiftUI; }; + D08E52B7257C62D500FA2C5F /* ViewModels */ = { + isa = XCSwiftPackageProductDependency; + productName = ViewModels; + }; D0BECB972501C0FC002C1B13 /* Secrets */ = { isa = XCSwiftPackageProductDependency; productName = Secrets; diff --git a/ServiceLayer/Sources/ServiceLayer/Services/IdentityService.swift b/ServiceLayer/Sources/ServiceLayer/Services/IdentityService.swift index 46842e6..9af703a 100644 --- a/ServiceLayer/Sources/ServiceLayer/Services/IdentityService.swift +++ b/ServiceLayer/Sources/ServiceLayer/Services/IdentityService.swift @@ -228,6 +228,10 @@ public extension IdentityService { func domainBlocksService() -> DomainBlocksService { DomainBlocksService(mastodonAPIClient: mastodonAPIClient) } + + func newStatusService() -> NewStatusService { + NewStatusService(id: id, identityDatabase: identityDatabase, environment: environment) + } } private extension IdentityService { diff --git a/ServiceLayer/Sources/ServiceLayer/Services/NewStatusService.swift b/ServiceLayer/Sources/ServiceLayer/Services/NewStatusService.swift new file mode 100644 index 0000000..b87ad3e --- /dev/null +++ b/ServiceLayer/Sources/ServiceLayer/Services/NewStatusService.swift @@ -0,0 +1,35 @@ +// Copyright © 2020 Metabolist. All rights reserved. + +import Combine +import DB +import Foundation +import Mastodon +import MastodonAPI +import Secrets + +public struct NewStatusService { + private var id: Identity.Id + private let identityDatabase: IdentityDatabase + private let environment: AppEnvironment + + public init(id: Identity.Id, identityDatabase: IdentityDatabase, environment: AppEnvironment) { + self.id = id + self.identityDatabase = identityDatabase + self.environment = environment + } +} + +extension NewStatusService { + func mastodonAPIClient() throws -> MastodonAPIClient { + let secrets = Secrets( + identityId: id, + keychain: environment.keychain) + let mastodonAPIClient = MastodonAPIClient( + session: environment.session, + instanceURL: try secrets.getInstanceURL()) + + mastodonAPIClient.accessToken = try secrets.getAccessToken() + + return mastodonAPIClient + } +} diff --git a/Share Extension/Info.plist b/Share Extension/Info.plist new file mode 100644 index 0000000..40936e6 --- /dev/null +++ b/Share Extension/Info.plist @@ -0,0 +1,47 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Share Extension + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + $(MARKETING_VERSION) + CFBundleVersion + 1 + NSExtension + + NSExtensionAttributes + + NSExtensionActivationRule + + NSExtensionActivationSupportsImageWithMaxCount + 4 + NSExtensionActivationSupportsText + + NSExtensionActivationSupportsMovieWithMaxCount + 1 + NSExtensionActivationSupportsWebPageWithMaxCount + 1 + NSExtensionActivationSupportsWebURLWithMaxCount + 1 + + + NSExtensionPointIdentifier + com.apple.share-services + NSExtensionPrincipalClass + ShareExtensionNavigationViewController + + + diff --git a/Share Extension/Share Extension.entitlements b/Share Extension/Share Extension.entitlements new file mode 100644 index 0000000..cd41796 --- /dev/null +++ b/Share Extension/Share Extension.entitlements @@ -0,0 +1,18 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.application-groups + + group.metabolist.metatext + + com.apple.security.network.client + + keychain-access-groups + + $(AppIdentifierPrefix)com.metabolist.metatext + + + diff --git a/Share Extension/ShareErrorViewController.swift b/Share Extension/ShareErrorViewController.swift new file mode 100644 index 0000000..bcf215b --- /dev/null +++ b/Share Extension/ShareErrorViewController.swift @@ -0,0 +1,44 @@ +// Copyright © 2020 Metabolist. All rights reserved. + +import UIKit + +class ShareErrorViewController: UIViewController { + let error: Error + + init(error: Error) { + self.error = error + + super.init(nibName: nil, bundle: nil) + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + let label = UILabel() + + override func viewDidLoad() { + super.viewDidLoad() + + view.backgroundColor = .systemBackground + + view.addSubview(label) + label.translatesAutoresizingMaskIntoConstraints = false + label.adjustsFontForContentSizeCategory = true + label.textAlignment = .center + label.font = .preferredFont(forTextStyle: .callout) + label.text = (error as? LocalizedError)?.errorDescription ?? NSLocalizedString("error", comment: "") + + navigationItem.leftBarButtonItem = .init( + systemItem: .close, + primaryAction: UIAction { [weak self] _ in self?.extensionContext?.completeRequest(returningItems: nil) }) + + NSLayoutConstraint.activate([ + label.leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor), + label.topAnchor.constraint(equalTo: view.layoutMarginsGuide.topAnchor), + label.trailingAnchor.constraint(equalTo: view.layoutMarginsGuide.trailingAnchor), + label.bottomAnchor.constraint(equalTo: view.layoutMarginsGuide.bottomAnchor) + ]) + } +} diff --git a/Share Extension/ShareExtensionError.swift b/Share Extension/ShareExtensionError.swift new file mode 100644 index 0000000..125604b --- /dev/null +++ b/Share Extension/ShareExtensionError.swift @@ -0,0 +1,16 @@ +// Copyright © 2020 Metabolist. All rights reserved. + +import Foundation + +enum ShareExtensionError: Error { + case noAccountFound +} + +extension ShareExtensionError: LocalizedError { + var errorDescription: String? { + switch self { + case .noAccountFound: + return NSLocalizedString("share-extension-error.no-account-found", comment: "") + } + } +} diff --git a/Share Extension/ShareExtensionNavigationViewController.swift b/Share Extension/ShareExtensionNavigationViewController.swift new file mode 100644 index 0000000..8db7474 --- /dev/null +++ b/Share Extension/ShareExtensionNavigationViewController.swift @@ -0,0 +1,50 @@ +// Copyright © 2020 Metabolist. All rights reserved. + +import Combine +import ServiceLayer +import UIKit +import ViewModels + +@objc(ShareExtensionNavigationViewController) +class ShareExtensionNavigationViewController: UINavigationController { + override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { + super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) + + let vm: NewStatusViewModel + + do { + vm = try newStatusViewModel() + } catch { + setViewControllers([ShareErrorViewController(error: error)], animated: false) + + return + } + + setViewControllers([NewStatusViewController(viewModel: vm)], animated: false) + } + + @available(*, unavailable) + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +private extension ShareExtensionNavigationViewController { + func newStatusViewModel() throws -> NewStatusViewModel { + let environment = AppEnvironment.live( + userNotificationCenter: .current(), + reduceMotion: { UIAccessibility.isReduceMotionEnabled }) + let allIdentitiesService = try AllIdentitiesService(environment: environment) + + var id: Identity.Id? + + _ = allIdentitiesService.immediateMostRecentlyUsedIdentityIdPublisher() + .sink { _ in } receiveValue: { id = $0 } + + guard let idd = id else { throw ShareExtensionError.noAccountFound } + + let newStatusService = try allIdentitiesService.identityService(id: idd).newStatusService() + + return NewStatusViewModel(service: newStatusService) + } +} diff --git a/View Controllers/NewStatusViewController.swift b/View Controllers/NewStatusViewController.swift new file mode 100644 index 0000000..bf8bc6c --- /dev/null +++ b/View Controllers/NewStatusViewController.swift @@ -0,0 +1,37 @@ +// Copyright © 2020 Metabolist. All rights reserved. + +import UIKit +import ViewModels + +class NewStatusViewController: UIViewController { + private let viewModel: NewStatusViewModel + + init(viewModel: NewStatusViewModel) { + self.viewModel = viewModel + + super.init(nibName: nil, bundle: nil) + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + + view.backgroundColor = .systemBackground + + navigationItem.leftBarButtonItem = .init( + systemItem: .close, + primaryAction: UIAction { [weak self] _ in self?.extensionContext?.completeRequest(returningItems: nil) }) + } + + override func didMove(toParent parent: UIViewController?) { + super.didMove(toParent: parent) + + parent?.navigationItem.leftBarButtonItem = .init( + systemItem: .close, + primaryAction: UIAction { [weak self] _ in self?.presentingViewController?.dismiss(animated: true) }) + } +} diff --git a/ViewModels/Sources/PreviewViewModels/PreviewViewModels.swift b/ViewModels/Sources/PreviewViewModels/PreviewViewModels.swift index f517cd1..14d3fae 100644 --- a/ViewModels/Sources/PreviewViewModels/PreviewViewModels.swift +++ b/ViewModels/Sources/PreviewViewModels/PreviewViewModels.swift @@ -90,4 +90,12 @@ public extension DomainBlocksViewModel { static let preview = DomainBlocksViewModel(service: .init(mastodonAPIClient: .preview)) } +public extension NewStatusViewModel { + static let preview = NewStatusViewModel( + service: .init( + id: identityId, + identityDatabase: db, + environment: environment)) +} + // swiftlint:enable force_try diff --git a/ViewModels/Sources/ViewModels/NavigationViewModel.swift b/ViewModels/Sources/ViewModels/NavigationViewModel.swift index a653744..923d042 100644 --- a/ViewModels/Sources/ViewModels/NavigationViewModel.swift +++ b/ViewModels/Sources/ViewModels/NavigationViewModel.swift @@ -17,6 +17,7 @@ public final class NavigationViewModel: ObservableObject { } @Published public private(set) var timelinesAndLists: [Timeline] @Published public var presentingSecondaryNavigation = false + @Published public var presentingNewStatus = false @Published public var alertItem: AlertItem? public private(set) var timelineViewModel: CollectionItemsViewModel @@ -152,6 +153,10 @@ public extension NavigationViewModel { collectionService: identification.service.service(timeline: .bookmarks), identification: identification) } + + func newStatusViewModel() -> NewStatusViewModel { + NewStatusViewModel(service: identification.service.newStatusService()) + } } extension NavigationViewModel.Tab: Identifiable { diff --git a/ViewModels/Sources/ViewModels/NewStatusViewModel.swift b/ViewModels/Sources/ViewModels/NewStatusViewModel.swift new file mode 100644 index 0000000..bf496c6 --- /dev/null +++ b/ViewModels/Sources/ViewModels/NewStatusViewModel.swift @@ -0,0 +1,14 @@ +// Copyright © 2020 Metabolist. All rights reserved. + +import Combine +import Foundation +import Mastodon +import ServiceLayer + +public final class NewStatusViewModel: ObservableObject { + private let service: NewStatusService + + public init(service: NewStatusService) { + self.service = service + } +} diff --git a/Views/NewStatusView.swift b/Views/NewStatusView.swift new file mode 100644 index 0000000..569016f --- /dev/null +++ b/Views/NewStatusView.swift @@ -0,0 +1,22 @@ +// Copyright © 2020 Metabolist. All rights reserved. + +import SwiftUI +import ViewModels + +struct NewStatusView: UIViewControllerRepresentable { + let viewModelClosure: () -> NewStatusViewModel + + func makeUIViewController(context: Context) -> NewStatusViewController { + NewStatusViewController(viewModel: viewModelClosure()) + } + + func updateUIViewController(_ uiViewController: NewStatusViewController, context: Context) { + + } +} + +struct NewStatusView_Previews: PreviewProvider { + static var previews: some View { + NewStatusView { .preview } + } +} diff --git a/Views/TabNavigationView.swift b/Views/TabNavigationView.swift index 2d60ec5..e4b2bb5 100644 --- a/Views/TabNavigationView.swift +++ b/Views/TabNavigationView.swift @@ -37,6 +37,18 @@ struct TabNavigationView: View { .environmentObject(viewModel) .environmentObject(rootViewModel) } + .background( + EmptyView() + .fullScreenCover(isPresented: $viewModel.presentingNewStatus) { + NavigationView { + NewStatusView(viewModelClosure: viewModel.newStatusViewModel) + .edgesIgnoringSafeArea(.all) + .navigationBarTitleDisplayMode(.inline) + } + .navigationViewStyle(StackNavigationViewStyle()) + .environmentObject(viewModel) + .environmentObject(rootViewModel) + }) .alertItem($viewModel.alertItem) .onAppear(perform: viewModel.refreshIdentity) .onReceive(NotificationCenter.default @@ -152,7 +164,7 @@ private extension TabNavigationView { if viewModel.identification.identity.authenticated && !viewModel.identification.identity.pending { Button { - + viewModel.presentingNewStatus = true } label: { ZStack { Circle()