feat: restore share action extension
This commit is contained in:
parent
91bfc8ad5a
commit
939429aacc
|
@ -371,10 +371,10 @@
|
||||||
DBB8AB4F26AED13F00F6D281 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DB427DDE25BAA00100D1B89D /* Assets.xcassets */; };
|
DBB8AB4F26AED13F00F6D281 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DB427DDE25BAA00100D1B89D /* Assets.xcassets */; };
|
||||||
DBB9759C262462E1004620BD /* ThreadMetaView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB9759B262462E1004620BD /* ThreadMetaView.swift */; };
|
DBB9759C262462E1004620BD /* ThreadMetaView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB9759B262462E1004620BD /* ThreadMetaView.swift */; };
|
||||||
DBBE1B4525F3474B0081417A /* MastodonPickServerAppearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBE1B4425F3474B0081417A /* MastodonPickServerAppearance.swift */; };
|
DBBE1B4525F3474B0081417A /* MastodonPickServerAppearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBE1B4425F3474B0081417A /* MastodonPickServerAppearance.swift */; };
|
||||||
DBC6461526A170AB00B0E31B /* ComposeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBC6461426A170AB00B0E31B /* ComposeViewController.swift */; };
|
DBC3872429214121001EC0FD /* ShareViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBC3872329214121001EC0FD /* ShareViewController.swift */; };
|
||||||
DBC6461826A170AB00B0E31B /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = DBC6461626A170AB00B0E31B /* MainInterface.storyboard */; };
|
DBC6461826A170AB00B0E31B /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = DBC6461626A170AB00B0E31B /* MainInterface.storyboard */; };
|
||||||
DBC6461C26A170AB00B0E31B /* ShareActionExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = DBC6461226A170AB00B0E31B /* ShareActionExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
DBC6461C26A170AB00B0E31B /* ShareActionExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = DBC6461226A170AB00B0E31B /* ShareActionExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||||
DBC6462326A1712000B0E31B /* ComposeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBC6462226A1712000B0E31B /* ComposeViewModel.swift */; };
|
DBC6462326A1712000B0E31B /* ShareViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBC6462226A1712000B0E31B /* ShareViewModel.swift */; };
|
||||||
DBC6462826A1736300B0E31B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DB427DDE25BAA00100D1B89D /* Assets.xcassets */; };
|
DBC6462826A1736300B0E31B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DB427DDE25BAA00100D1B89D /* Assets.xcassets */; };
|
||||||
DBC7A672260C897100E57475 /* StatusContentWarningEditorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBC7A671260C897100E57475 /* StatusContentWarningEditorView.swift */; };
|
DBC7A672260C897100E57475 /* StatusContentWarningEditorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBC7A671260C897100E57475 /* StatusContentWarningEditorView.swift */; };
|
||||||
DBCA0EBC282BB38A0029E2B0 /* PageboyNavigateable.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBCA0EBB282BB38A0029E2B0 /* PageboyNavigateable.swift */; };
|
DBCA0EBC282BB38A0029E2B0 /* PageboyNavigateable.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBCA0EBB282BB38A0029E2B0 /* PageboyNavigateable.swift */; };
|
||||||
|
@ -950,11 +950,11 @@
|
||||||
DBB9759B262462E1004620BD /* ThreadMetaView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadMetaView.swift; sourceTree = "<group>"; };
|
DBB9759B262462E1004620BD /* ThreadMetaView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadMetaView.swift; sourceTree = "<group>"; };
|
||||||
DBBC24A726A52F9000398BB9 /* ComposeToolbarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeToolbarView.swift; sourceTree = "<group>"; };
|
DBBC24A726A52F9000398BB9 /* ComposeToolbarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeToolbarView.swift; sourceTree = "<group>"; };
|
||||||
DBBE1B4425F3474B0081417A /* MastodonPickServerAppearance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonPickServerAppearance.swift; sourceTree = "<group>"; };
|
DBBE1B4425F3474B0081417A /* MastodonPickServerAppearance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonPickServerAppearance.swift; sourceTree = "<group>"; };
|
||||||
|
DBC3872329214121001EC0FD /* ShareViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareViewController.swift; sourceTree = "<group>"; };
|
||||||
DBC6461226A170AB00B0E31B /* ShareActionExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = ShareActionExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
|
DBC6461226A170AB00B0E31B /* ShareActionExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = ShareActionExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
DBC6461426A170AB00B0E31B /* ComposeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeViewController.swift; sourceTree = "<group>"; };
|
|
||||||
DBC6461726A170AB00B0E31B /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/MainInterface.storyboard; sourceTree = "<group>"; };
|
DBC6461726A170AB00B0E31B /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/MainInterface.storyboard; sourceTree = "<group>"; };
|
||||||
DBC6461926A170AB00B0E31B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
DBC6461926A170AB00B0E31B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
DBC6462226A1712000B0E31B /* ComposeViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ComposeViewModel.swift; sourceTree = "<group>"; };
|
DBC6462226A1712000B0E31B /* ShareViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ShareViewModel.swift; sourceTree = "<group>"; };
|
||||||
DBC7A671260C897100E57475 /* StatusContentWarningEditorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusContentWarningEditorView.swift; sourceTree = "<group>"; };
|
DBC7A671260C897100E57475 /* StatusContentWarningEditorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusContentWarningEditorView.swift; sourceTree = "<group>"; };
|
||||||
DBC9E3A3282E13BB0063A4D9 /* eu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = eu; path = eu.lproj/Intents.strings; sourceTree = "<group>"; };
|
DBC9E3A3282E13BB0063A4D9 /* eu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = eu; path = eu.lproj/Intents.strings; sourceTree = "<group>"; };
|
||||||
DBC9E3A4282E13BB0063A4D9 /* eu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = eu; path = eu.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
DBC9E3A4282E13BB0063A4D9 /* eu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = eu; path = eu.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
||||||
|
@ -2686,8 +2686,8 @@
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
DBFEF05426A576EE006D7ED1 /* View */,
|
DBFEF05426A576EE006D7ED1 /* View */,
|
||||||
DBC6462226A1712000B0E31B /* ComposeViewModel.swift */,
|
DBC6462226A1712000B0E31B /* ShareViewModel.swift */,
|
||||||
DBC6461426A170AB00B0E31B /* ComposeViewController.swift */,
|
DBC3872329214121001EC0FD /* ShareViewController.swift */,
|
||||||
);
|
);
|
||||||
path = Scene;
|
path = Scene;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -3539,9 +3539,9 @@
|
||||||
isa = PBXSourcesBuildPhase;
|
isa = PBXSourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
DBC6462326A1712000B0E31B /* ComposeViewModel.swift in Sources */,
|
DBC6462326A1712000B0E31B /* ShareViewModel.swift in Sources */,
|
||||||
DBB3BA2B26A81D060004F2D4 /* FLAnimatedImageView.swift in Sources */,
|
DBB3BA2B26A81D060004F2D4 /* FLAnimatedImageView.swift in Sources */,
|
||||||
DBC6461526A170AB00B0E31B /* ComposeViewController.swift in Sources */,
|
DBC3872429214121001EC0FD /* ShareViewController.swift in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
|
|
@ -117,7 +117,7 @@
|
||||||
<key>NotificationService.xcscheme_^#shared#^_</key>
|
<key>NotificationService.xcscheme_^#shared#^_</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>orderHint</key>
|
<key>orderHint</key>
|
||||||
<integer>18</integer>
|
<integer>16</integer>
|
||||||
</dict>
|
</dict>
|
||||||
<key>ShareActionExtension.xcscheme_^#shared#^_</key>
|
<key>ShareActionExtension.xcscheme_^#shared#^_</key>
|
||||||
<dict>
|
<dict>
|
||||||
|
|
|
@ -120,9 +120,9 @@ extension ComposeViewController {
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
guard self.traitCollection.userInterfaceIdiom == .pad else { return }
|
guard self.traitCollection.userInterfaceIdiom == .pad else { return }
|
||||||
var items = [self.publishBarButtonItem]
|
var items = [self.publishBarButtonItem]
|
||||||
if self.traitCollection.horizontalSizeClass == .regular {
|
// if self.traitCollection.horizontalSizeClass == .regular {
|
||||||
items.append(self.characterCountBarButtonItem)
|
// items.append(self.characterCountBarButtonItem)
|
||||||
}
|
// }
|
||||||
self.navigationItem.rightBarButtonItems = items
|
self.navigationItem.rightBarButtonItems = items
|
||||||
}
|
}
|
||||||
.store(in: &disposeBag)
|
.store(in: &disposeBag)
|
||||||
|
@ -140,7 +140,7 @@ extension ComposeViewController {
|
||||||
composeContentViewController.didMove(toParent: self)
|
composeContentViewController.didMove(toParent: self)
|
||||||
|
|
||||||
// bind navigation bar style
|
// bind navigation bar style
|
||||||
configureNavigationBarTitleStyle()
|
// configureNavigationBarTitleStyle()
|
||||||
viewModel.traitCollectionDidChangePublisher
|
viewModel.traitCollectionDidChangePublisher
|
||||||
.receive(on: DispatchQueue.main)
|
.receive(on: DispatchQueue.main)
|
||||||
.sink { [weak self] _ in
|
.sink { [weak self] _ in
|
||||||
|
@ -163,24 +163,6 @@ extension ComposeViewController {
|
||||||
.receive(on: DispatchQueue.main)
|
.receive(on: DispatchQueue.main)
|
||||||
.assign(to: \.isEnabled, on: publishButton)
|
.assign(to: \.isEnabled, on: publishButton)
|
||||||
.store(in: &disposeBag)
|
.store(in: &disposeBag)
|
||||||
//
|
|
||||||
// // bind content warning button state
|
|
||||||
// viewModel.$isContentWarningComposing
|
|
||||||
// .receive(on: DispatchQueue.main)
|
|
||||||
// .sink { [weak self] isContentWarningComposing in
|
|
||||||
// guard let self = self else { return }
|
|
||||||
// let accessibilityLabel = isContentWarningComposing ? L10n.Scene.Compose.Accessibility.disableContentWarning : L10n.Scene.Compose.Accessibility.enableContentWarning
|
|
||||||
// self.composeToolbarView.contentWarningBarButtonItem.accessibilityLabel = accessibilityLabel
|
|
||||||
// self.composeToolbarView.contentWarningButton.accessibilityLabel = accessibilityLabel
|
|
||||||
// }
|
|
||||||
// .store(in: &disposeBag)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
override func viewDidAppear(_ animated: Bool) {
|
|
||||||
super.viewDidAppear(animated)
|
|
||||||
|
|
||||||
// viewModel.isViewAppeared = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
|
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
|
||||||
|
|
|
@ -1,326 +0,0 @@
|
||||||
//
|
|
||||||
// ComposeViewController.swift
|
|
||||||
// MastodonShareAction
|
|
||||||
//
|
|
||||||
// Created by MainasuK Cirno on 2021-7-16.
|
|
||||||
//
|
|
||||||
|
|
||||||
import os.log
|
|
||||||
import UIKit
|
|
||||||
import Combine
|
|
||||||
import SwiftUI
|
|
||||||
import MastodonAsset
|
|
||||||
import MastodonLocalization
|
|
||||||
import MastodonCore
|
|
||||||
import MastodonUI
|
|
||||||
|
|
||||||
class ComposeViewController: UIViewController {
|
|
||||||
|
|
||||||
let logger = Logger(subsystem: "ComposeViewController", category: "ViewController")
|
|
||||||
|
|
||||||
let context = AppContext()
|
|
||||||
|
|
||||||
var disposeBag = Set<AnyCancellable>()
|
|
||||||
private(set) lazy var viewModel = ComposeViewModel(context: context)
|
|
||||||
|
|
||||||
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.brand.color), for: .normal)
|
|
||||||
button.setBackgroundImage(.placeholder(color: Asset.Colors.brand.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(ComposeViewController.cancelBarButtonItemPressed(_:)))
|
|
||||||
private(set) lazy var publishBarButtonItem: UIBarButtonItem = {
|
|
||||||
let barButtonItem = UIBarButtonItem(customView: publishButton)
|
|
||||||
publishButton.addTarget(self, action: #selector(ComposeViewController.publishBarButtonItemPressed(_:)), for: .touchUpInside)
|
|
||||||
return barButtonItem
|
|
||||||
}()
|
|
||||||
|
|
||||||
let activityIndicatorBarButtonItem: UIBarButtonItem = {
|
|
||||||
let indicatorView = UIActivityIndicatorView(style: .medium)
|
|
||||||
let barButtonItem = UIBarButtonItem(customView: indicatorView)
|
|
||||||
indicatorView.startAnimating()
|
|
||||||
return barButtonItem
|
|
||||||
}()
|
|
||||||
|
|
||||||
|
|
||||||
// let viewSafeAreaDidChange = PassthroughSubject<Void, Never>()
|
|
||||||
// let composeToolbarView = ComposeToolbarView()
|
|
||||||
// var composeToolbarViewBottomLayoutConstraint: NSLayoutConstraint!
|
|
||||||
// let composeToolbarBackgroundView = UIView()
|
|
||||||
}
|
|
||||||
|
|
||||||
extension ComposeViewController {
|
|
||||||
|
|
||||||
override func viewDidLoad() {
|
|
||||||
super.viewDidLoad()
|
|
||||||
|
|
||||||
// navigationController?.presentationController?.delegate = self
|
|
||||||
//
|
|
||||||
// setupBackgroundColor(theme: ThemeService.shared.currentTheme.value)
|
|
||||||
// ThemeService.shared.currentTheme
|
|
||||||
// .receive(on: DispatchQueue.main)
|
|
||||||
// .sink { [weak self] theme in
|
|
||||||
// guard let self = self else { return }
|
|
||||||
// self.setupBackgroundColor(theme: theme)
|
|
||||||
// }
|
|
||||||
// .store(in: &disposeBag)
|
|
||||||
//
|
|
||||||
// navigationItem.leftBarButtonItem = cancelBarButtonItem
|
|
||||||
// viewModel.isBusy
|
|
||||||
// .receive(on: DispatchQueue.main)
|
|
||||||
// .sink { [weak self] isBusy in
|
|
||||||
// guard let self = self else { return }
|
|
||||||
// self.navigationItem.rightBarButtonItem = isBusy ? self.activityIndicatorBarButtonItem : self.publishBarButtonItem
|
|
||||||
// }
|
|
||||||
// .store(in: &disposeBag)
|
|
||||||
//
|
|
||||||
// let hostingViewController = UIHostingController(
|
|
||||||
// rootView: ComposeView().environmentObject(viewModel.composeViewModel)
|
|
||||||
// )
|
|
||||||
// addChild(hostingViewController)
|
|
||||||
// view.addSubview(hostingViewController.view)
|
|
||||||
// hostingViewController.view.translatesAutoresizingMaskIntoConstraints = false
|
|
||||||
// view.addSubview(hostingViewController.view)
|
|
||||||
// NSLayoutConstraint.activate([
|
|
||||||
// hostingViewController.view.topAnchor.constraint(equalTo: view.topAnchor),
|
|
||||||
// hostingViewController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor),
|
|
||||||
// hostingViewController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor),
|
|
||||||
// hostingViewController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor),
|
|
||||||
// ])
|
|
||||||
// hostingViewController.didMove(toParent: self)
|
|
||||||
//
|
|
||||||
// composeToolbarView.translatesAutoresizingMaskIntoConstraints = false
|
|
||||||
// view.addSubview(composeToolbarView)
|
|
||||||
// composeToolbarViewBottomLayoutConstraint = view.bottomAnchor.constraint(equalTo: composeToolbarView.bottomAnchor)
|
|
||||||
// NSLayoutConstraint.activate([
|
|
||||||
// composeToolbarView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
|
|
||||||
// composeToolbarView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
|
|
||||||
// composeToolbarViewBottomLayoutConstraint,
|
|
||||||
// composeToolbarView.heightAnchor.constraint(equalToConstant: ComposeToolbarView.toolbarHeight),
|
|
||||||
// ])
|
|
||||||
// composeToolbarView.preservesSuperviewLayoutMargins = true
|
|
||||||
// composeToolbarView.delegate = self
|
|
||||||
//
|
|
||||||
// composeToolbarBackgroundView.translatesAutoresizingMaskIntoConstraints = false
|
|
||||||
// view.insertSubview(composeToolbarBackgroundView, belowSubview: composeToolbarView)
|
|
||||||
// NSLayoutConstraint.activate([
|
|
||||||
// composeToolbarBackgroundView.topAnchor.constraint(equalTo: composeToolbarView.topAnchor),
|
|
||||||
// composeToolbarBackgroundView.leadingAnchor.constraint(equalTo: composeToolbarView.leadingAnchor),
|
|
||||||
// composeToolbarBackgroundView.trailingAnchor.constraint(equalTo: composeToolbarView.trailingAnchor),
|
|
||||||
// view.bottomAnchor.constraint(equalTo: composeToolbarBackgroundView.bottomAnchor),
|
|
||||||
// ])
|
|
||||||
//
|
|
||||||
// // FIXME: using iOS 15 toolbar for .keyboard placement
|
|
||||||
// let keyboardEventPublishers = Publishers.CombineLatest3(
|
|
||||||
// KeyboardResponderService.shared.isShow,
|
|
||||||
// KeyboardResponderService.shared.state,
|
|
||||||
// KeyboardResponderService.shared.endFrame
|
|
||||||
// )
|
|
||||||
//
|
|
||||||
// Publishers.CombineLatest(
|
|
||||||
// keyboardEventPublishers,
|
|
||||||
// viewSafeAreaDidChange
|
|
||||||
// )
|
|
||||||
// .sink(receiveValue: { [weak self] keyboardEvents, _ in
|
|
||||||
// guard let self = self else { return }
|
|
||||||
//
|
|
||||||
// let (isShow, state, endFrame) = keyboardEvents
|
|
||||||
// guard isShow, state == .dock else {
|
|
||||||
// UIView.animate(withDuration: 0.3) {
|
|
||||||
// self.composeToolbarViewBottomLayoutConstraint.constant = self.view.safeAreaInsets.bottom
|
|
||||||
// self.view.layoutIfNeeded()
|
|
||||||
// }
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
// // isShow AND dock state
|
|
||||||
//
|
|
||||||
// UIView.animate(withDuration: 0.3) {
|
|
||||||
// self.composeToolbarViewBottomLayoutConstraint.constant = endFrame.height
|
|
||||||
// self.view.layoutIfNeeded()
|
|
||||||
// }
|
|
||||||
// })
|
|
||||||
// .store(in: &disposeBag)
|
|
||||||
//
|
|
||||||
// // bind visibility toolbar UI
|
|
||||||
// Publishers.CombineLatest(
|
|
||||||
// viewModel.selectedStatusVisibility,
|
|
||||||
// viewModel.traitCollectionDidChangePublisher
|
|
||||||
// )
|
|
||||||
// .receive(on: DispatchQueue.main)
|
|
||||||
// .sink { [weak self] type, _ in
|
|
||||||
// guard let self = self else { return }
|
|
||||||
// let image = type.image(interfaceStyle: self.traitCollection.userInterfaceStyle)
|
|
||||||
// self.composeToolbarView.visibilityButton.setImage(image, for: .normal)
|
|
||||||
// self.composeToolbarView.activeVisibilityType.value = type
|
|
||||||
// }
|
|
||||||
// .store(in: &disposeBag)
|
|
||||||
//
|
|
||||||
// // bind counter
|
|
||||||
// viewModel.characterCount
|
|
||||||
// .receive(on: DispatchQueue.main)
|
|
||||||
// .sink { [weak self] characterCount in
|
|
||||||
// guard let self = self else { return }
|
|
||||||
// let count = ShareViewModel.composeContentLimit - characterCount
|
|
||||||
// self.composeToolbarView.characterCountLabel.text = "\(count)"
|
|
||||||
// switch count {
|
|
||||||
// case _ where count < 0:
|
|
||||||
// self.composeToolbarView.characterCountLabel.font = .monospacedDigitSystemFont(ofSize: 24, weight: .bold)
|
|
||||||
// self.composeToolbarView.characterCountLabel.textColor = Asset.Colors.danger.color
|
|
||||||
// self.composeToolbarView.characterCountLabel.accessibilityLabel = L10n.A11y.Plural.Count.inputLimitExceeds(abs(count))
|
|
||||||
// default:
|
|
||||||
// self.composeToolbarView.characterCountLabel.font = .monospacedDigitSystemFont(ofSize: 15, weight: .regular)
|
|
||||||
// self.composeToolbarView.characterCountLabel.textColor = Asset.Colors.Label.secondary.color
|
|
||||||
// self.composeToolbarView.characterCountLabel.accessibilityLabel = L10n.A11y.Plural.Count.inputLimitRemains(count)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// .store(in: &disposeBag)
|
|
||||||
//
|
|
||||||
// // bind valid
|
|
||||||
// viewModel.isValid
|
|
||||||
// .receive(on: DispatchQueue.main)
|
|
||||||
// .assign(to: \.isEnabled, on: publishButton)
|
|
||||||
// .store(in: &disposeBag)
|
|
||||||
}
|
|
||||||
|
|
||||||
override func viewDidAppear(_ animated: Bool) {
|
|
||||||
super.viewDidAppear(animated)
|
|
||||||
|
|
||||||
// viewModel.viewDidAppear.value = true
|
|
||||||
// viewModel.inputItems.value = extensionContext?.inputItems.compactMap { $0 as? NSExtensionItem } ?? []
|
|
||||||
//
|
|
||||||
// viewModel.composeViewModel.viewDidAppear = true
|
|
||||||
}
|
|
||||||
|
|
||||||
override func viewSafeAreaInsetsDidChange() {
|
|
||||||
super.viewSafeAreaInsetsDidChange()
|
|
||||||
|
|
||||||
// viewSafeAreaDidChange.send()
|
|
||||||
}
|
|
||||||
|
|
||||||
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
|
|
||||||
super.traitCollectionDidChange(previousTraitCollection)
|
|
||||||
|
|
||||||
// viewModel.traitCollectionDidChangePublisher.send()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
//extension ComposeViewController {
|
|
||||||
// private func setupBackgroundColor(theme: Theme) {
|
|
||||||
// view.backgroundColor = theme.systemElevatedBackgroundColor
|
|
||||||
// viewModel.composeViewModel.backgroundColor = theme.systemElevatedBackgroundColor
|
|
||||||
// composeToolbarBackgroundView.backgroundColor = theme.composeToolbarBackgroundColor
|
|
||||||
//
|
|
||||||
// let barAppearance = UINavigationBarAppearance()
|
|
||||||
// barAppearance.configureWithDefaultBackground()
|
|
||||||
// barAppearance.backgroundColor = theme.navigationBarBackgroundColor
|
|
||||||
// navigationItem.standardAppearance = barAppearance
|
|
||||||
// navigationItem.compactAppearance = barAppearance
|
|
||||||
// navigationItem.scrollEdgeAppearance = barAppearance
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// private func showDismissConfirmAlertController() {
|
|
||||||
// let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) // can not use alert in extension
|
|
||||||
// let discardAction = UIAlertAction(title: L10n.Common.Controls.Actions.discard, style: .destructive) { _ in
|
|
||||||
// self.extensionContext?.cancelRequest(withError: ShareViewModel.ShareError.userCancelShare)
|
|
||||||
// }
|
|
||||||
// alertController.addAction(discardAction)
|
|
||||||
// let okAction = UIAlertAction(title: L10n.Common.Controls.Actions.ok, style: .cancel, handler: nil)
|
|
||||||
// alertController.addAction(okAction)
|
|
||||||
// self.present(alertController, animated: true, completion: nil)
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
//
|
|
||||||
extension ComposeViewController {
|
|
||||||
@objc private func cancelBarButtonItemPressed(_ sender: UIBarButtonItem) {
|
|
||||||
logger.debug("\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public)")
|
|
||||||
|
|
||||||
// showDismissConfirmAlertController()
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc private func publishBarButtonItemPressed(_ sender: UIBarButtonItem) {
|
|
||||||
logger.debug("\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public)")
|
|
||||||
|
|
||||||
// viewModel.isPublishing.value = true
|
|
||||||
//
|
|
||||||
// viewModel.publish()
|
|
||||||
// .delay(for: 2, scheduler: DispatchQueue.main)
|
|
||||||
// .receive(on: DispatchQueue.main)
|
|
||||||
// .sink { [weak self] completion in
|
|
||||||
// guard let self = self else { return }
|
|
||||||
// self.viewModel.isPublishing.value = false
|
|
||||||
//
|
|
||||||
// switch completion {
|
|
||||||
// case .failure:
|
|
||||||
// let alertController = UIAlertController(
|
|
||||||
// title: L10n.Common.Alerts.PublishPostFailure.title,
|
|
||||||
// message: L10n.Common.Alerts.PublishPostFailure.message,
|
|
||||||
// preferredStyle: .actionSheet // can not use alert in extension
|
|
||||||
// )
|
|
||||||
// let okAction = UIAlertAction(
|
|
||||||
// title: L10n.Common.Controls.Actions.ok,
|
|
||||||
// style: .cancel,
|
|
||||||
// handler: nil
|
|
||||||
// )
|
|
||||||
// alertController.addAction(okAction)
|
|
||||||
// self.present(alertController, animated: true, completion: nil)
|
|
||||||
// case .finished:
|
|
||||||
// self.publishButton.setTitle(L10n.Common.Controls.Actions.done, for: .normal)
|
|
||||||
// self.publishButton.isUserInteractionEnabled = false
|
|
||||||
// DispatchQueue.main.asyncAfter(deadline: .now() + 1) { [weak self] in
|
|
||||||
// guard let self = self else { return }
|
|
||||||
// self.extensionContext?.completeRequest(returningItems: nil, completionHandler: nil)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// } receiveValue: { response in
|
|
||||||
// // do nothing
|
|
||||||
// }
|
|
||||||
// .store(in: &disposeBag)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//// MARK - ComposeToolbarViewDelegate
|
|
||||||
//extension ComposeViewController: ComposeToolbarViewDelegate {
|
|
||||||
//
|
|
||||||
// func composeToolbarView(_ composeToolbarView: ComposeToolbarView, contentWarningButtonDidPressed sender: UIButton) {
|
|
||||||
// logger.debug("\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public)")
|
|
||||||
//
|
|
||||||
// withAnimation {
|
|
||||||
// viewModel.composeViewModel.isContentWarningComposing.toggle()
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// func composeToolbarView(_ composeToolbarView: ComposeToolbarView, visibilityButtonDidPressed sender: UIButton, visibilitySelectionType type: ComposeToolbarView.VisibilitySelectionType) {
|
|
||||||
// logger.debug("\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public)")
|
|
||||||
//
|
|
||||||
// viewModel.selectedStatusVisibility.value = type
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
//}
|
|
||||||
//
|
|
||||||
//// MARK: - UIAdaptivePresentationControllerDelegate
|
|
||||||
//extension ComposeViewController: UIAdaptivePresentationControllerDelegate {
|
|
||||||
//
|
|
||||||
// func presentationControllerShouldDismiss(_ presentationController: UIPresentationController) -> Bool {
|
|
||||||
// return viewModel.shouldDismiss.value
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// func presentationControllerDidAttemptToDismiss(_ presentationController: UIPresentationController) {
|
|
||||||
// os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
|
|
||||||
// showDismissConfirmAlertController()
|
|
||||||
//
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// func presentationControllerDidDismiss(_ presentationController: UIPresentationController) {
|
|
||||||
// os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
//}
|
|
|
@ -1,416 +0,0 @@
|
||||||
//
|
|
||||||
// ComposeViewModel.swift
|
|
||||||
// MastodonShareAction
|
|
||||||
//
|
|
||||||
// Created by MainasuK Cirno on 2021-7-16.
|
|
||||||
//
|
|
||||||
|
|
||||||
import os.log
|
|
||||||
import Foundation
|
|
||||||
import Combine
|
|
||||||
import CoreData
|
|
||||||
import CoreDataStack
|
|
||||||
import MastodonSDK
|
|
||||||
import SwiftUI
|
|
||||||
import UniformTypeIdentifiers
|
|
||||||
import MastodonAsset
|
|
||||||
import MastodonLocalization
|
|
||||||
import MastodonUI
|
|
||||||
import MastodonCore
|
|
||||||
|
|
||||||
final class ComposeViewModel {
|
|
||||||
|
|
||||||
let logger = Logger(subsystem: "ComposeViewModel", category: "ViewModel")
|
|
||||||
|
|
||||||
var disposeBag = Set<AnyCancellable>()
|
|
||||||
|
|
||||||
static let composeContentLimit: Int = 500
|
|
||||||
|
|
||||||
// input
|
|
||||||
let context: AppContext
|
|
||||||
|
|
||||||
// private var coreDataStack: CoreDataStack?
|
|
||||||
// var managedObjectContext: NSManagedObjectContext?
|
|
||||||
// var api: APIService?
|
|
||||||
//
|
|
||||||
// var inputItems = CurrentValueSubject<[NSExtensionItem], Never>([])
|
|
||||||
// let viewDidAppear = CurrentValueSubject<Bool, Never>(false)
|
|
||||||
// let traitCollectionDidChangePublisher = CurrentValueSubject<Void, Never>(Void()) // use CurrentValueSubject to make initial event emit
|
|
||||||
// let selectedStatusVisibility = CurrentValueSubject<ComposeToolbarView.VisibilitySelectionType, Never>(.public)
|
|
||||||
//
|
|
||||||
// // output
|
|
||||||
// let authentication = CurrentValueSubject<Result<MastodonAuthentication, Error>?, Never>(nil)
|
|
||||||
// let isFetchAuthentication = CurrentValueSubject<Bool, Never>(true)
|
|
||||||
// let isPublishing = CurrentValueSubject<Bool, Never>(false)
|
|
||||||
// let isBusy = CurrentValueSubject<Bool, Never>(true)
|
|
||||||
// let isValid = CurrentValueSubject<Bool, Never>(false)
|
|
||||||
// let shouldDismiss = CurrentValueSubject<Bool, Never>(true)
|
|
||||||
// let composeViewModel = ComposeViewModel()
|
|
||||||
// let characterCount = CurrentValueSubject<Int, Never>(0)
|
|
||||||
|
|
||||||
init(context: AppContext) {
|
|
||||||
self.context = context
|
|
||||||
// end 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)
|
|
||||||
//
|
|
||||||
// Publishers.CombineLatest(
|
|
||||||
// inputItems.removeDuplicates(),
|
|
||||||
// viewDidAppear.removeDuplicates()
|
|
||||||
// )
|
|
||||||
// .receive(on: DispatchQueue.main)
|
|
||||||
// .sink { [weak self] inputItems, _ in
|
|
||||||
// guard let self = self else { return }
|
|
||||||
// self.parse(inputItems: inputItems)
|
|
||||||
// }
|
|
||||||
// .store(in: &disposeBag)
|
|
||||||
//
|
|
||||||
// // bind authentication loading state
|
|
||||||
// authentication
|
|
||||||
// .map { result in result == nil }
|
|
||||||
// .assign(to: \.value, on: isFetchAuthentication)
|
|
||||||
// .store(in: &disposeBag)
|
|
||||||
//
|
|
||||||
// // bind user locked state
|
|
||||||
// authentication
|
|
||||||
// .compactMap { result -> Bool? in
|
|
||||||
// guard let result = result else { return nil }
|
|
||||||
// switch result {
|
|
||||||
// case .success(let authentication):
|
|
||||||
// return authentication.user.locked
|
|
||||||
// case .failure:
|
|
||||||
// return nil
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// .map { locked -> ComposeToolbarView.VisibilitySelectionType in
|
|
||||||
// locked ? .private : .public
|
|
||||||
// }
|
|
||||||
// .assign(to: \.value, on: selectedStatusVisibility)
|
|
||||||
// .store(in: &disposeBag)
|
|
||||||
//
|
|
||||||
// // bind author
|
|
||||||
// authentication
|
|
||||||
// .receive(on: DispatchQueue.main)
|
|
||||||
// .sink { [weak self] result in
|
|
||||||
// guard let self = self else { return }
|
|
||||||
// guard let result = result else { return }
|
|
||||||
// switch result {
|
|
||||||
// case .success(let authentication):
|
|
||||||
// self.composeViewModel.avatarImageURL = authentication.user.avatarImageURL()
|
|
||||||
// self.composeViewModel.authorName = authentication.user.displayNameWithFallback
|
|
||||||
// self.composeViewModel.authorUsername = "@" + authentication.user.username
|
|
||||||
// case .failure:
|
|
||||||
// self.composeViewModel.avatarImageURL = nil
|
|
||||||
// self.composeViewModel.authorName = " "
|
|
||||||
// self.composeViewModel.authorUsername = " "
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// .store(in: &disposeBag)
|
|
||||||
//
|
|
||||||
// // bind authentication to compose view model
|
|
||||||
// authentication
|
|
||||||
// .map { result -> MastodonAuthentication? in
|
|
||||||
// guard let result = result else { return nil }
|
|
||||||
// switch result {
|
|
||||||
// case .success(let authentication):
|
|
||||||
// return authentication
|
|
||||||
// case .failure:
|
|
||||||
// return nil
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// .assign(to: &composeViewModel.$authentication)
|
|
||||||
//
|
|
||||||
// // bind isBusy
|
|
||||||
// Publishers.CombineLatest(
|
|
||||||
// isFetchAuthentication,
|
|
||||||
// isPublishing
|
|
||||||
// )
|
|
||||||
// .receive(on: DispatchQueue.main)
|
|
||||||
// .map { $0 || $1 }
|
|
||||||
// .assign(to: \.value, on: isBusy)
|
|
||||||
// .store(in: &disposeBag)
|
|
||||||
//
|
|
||||||
// // pass initial i18n string
|
|
||||||
// composeViewModel.statusPlaceholder = L10n.Scene.Compose.contentInputPlaceholder
|
|
||||||
// composeViewModel.contentWarningPlaceholder = L10n.Scene.Compose.ContentWarning.placeholder
|
|
||||||
// composeViewModel.toolbarHeight = ComposeToolbarView.toolbarHeight
|
|
||||||
//
|
|
||||||
// // bind compose bar button item UI state
|
|
||||||
// let isComposeContentEmpty = composeViewModel.$statusContent
|
|
||||||
// .map { $0.isEmpty }
|
|
||||||
//
|
|
||||||
// isComposeContentEmpty
|
|
||||||
// .assign(to: \.value, on: shouldDismiss)
|
|
||||||
// .store(in: &disposeBag)
|
|
||||||
//
|
|
||||||
// let isComposeContentValid = composeViewModel.$characterCount
|
|
||||||
// .map { characterCount -> Bool in
|
|
||||||
// return characterCount <= ShareViewModel.composeContentLimit
|
|
||||||
// }
|
|
||||||
// let isMediaEmpty = composeViewModel.$attachmentViewModels
|
|
||||||
// .map { $0.isEmpty }
|
|
||||||
// let isMediaUploadAllSuccess = composeViewModel.$attachmentViewModels
|
|
||||||
// .map { viewModels in
|
|
||||||
// viewModels.allSatisfy { $0.uploadStateMachineSubject.value is StatusAttachmentViewModel.UploadState.Finish }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// let isPublishBarButtonItemEnabledPrecondition1 = Publishers.CombineLatest4(
|
|
||||||
// isComposeContentEmpty,
|
|
||||||
// isComposeContentValid,
|
|
||||||
// isMediaEmpty,
|
|
||||||
// isMediaUploadAllSuccess
|
|
||||||
// )
|
|
||||||
// .map { isComposeContentEmpty, isComposeContentValid, isMediaEmpty, isMediaUploadAllSuccess -> Bool in
|
|
||||||
// if isMediaEmpty {
|
|
||||||
// return isComposeContentValid && !isComposeContentEmpty
|
|
||||||
// } else {
|
|
||||||
// return isComposeContentValid && isMediaUploadAllSuccess
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// .eraseToAnyPublisher()
|
|
||||||
//
|
|
||||||
// let isPublishBarButtonItemEnabledPrecondition2 = Publishers.CombineLatest(
|
|
||||||
// isComposeContentEmpty,
|
|
||||||
// isComposeContentValid
|
|
||||||
// )
|
|
||||||
// .map { isComposeContentEmpty, isComposeContentValid -> Bool in
|
|
||||||
// return isComposeContentValid && !isComposeContentEmpty
|
|
||||||
// }
|
|
||||||
// .eraseToAnyPublisher()
|
|
||||||
//
|
|
||||||
// Publishers.CombineLatest(
|
|
||||||
// isPublishBarButtonItemEnabledPrecondition1,
|
|
||||||
// isPublishBarButtonItemEnabledPrecondition2
|
|
||||||
// )
|
|
||||||
// .map { $0 && $1 }
|
|
||||||
// .assign(to: \.value, on: isValid)
|
|
||||||
// .store(in: &disposeBag)
|
|
||||||
//
|
|
||||||
// // bind counter
|
|
||||||
// composeViewModel.$characterCount
|
|
||||||
// .assign(to: \.value, on: characterCount)
|
|
||||||
// .store(in: &disposeBag)
|
|
||||||
//
|
|
||||||
// // setup theme
|
|
||||||
// setupBackgroundColor(theme: ThemeService.shared.currentTheme.value)
|
|
||||||
// ThemeService.shared.currentTheme
|
|
||||||
// .receive(on: DispatchQueue.main)
|
|
||||||
// .sink { [weak self] theme in
|
|
||||||
// guard let self = self else { return }
|
|
||||||
// self.setupBackgroundColor(theme: theme)
|
|
||||||
// }
|
|
||||||
// .store(in: &disposeBag)
|
|
||||||
}
|
|
||||||
|
|
||||||
private func setupBackgroundColor(theme: Theme) {
|
|
||||||
// composeViewModel.contentWarningBackgroundColor = Color(theme.contentWarningOverlayBackgroundColor)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
//extension ShareViewModel {
|
|
||||||
// enum ShareError: Error {
|
|
||||||
// case `internal`(error: Error)
|
|
||||||
// case userCancelShare
|
|
||||||
// case missingAuthentication
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
|
|
||||||
extension ComposeViewModel {
|
|
||||||
// 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.api = APIService(backgroundManagedObjectContext: _coreDataStack.newTaskContext())
|
|
||||||
//
|
|
||||||
// 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)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
//extension ShareViewModel {
|
|
||||||
// func parse(inputItems: [NSExtensionItem]) {
|
|
||||||
// var itemProviders: [NSItemProvider] = []
|
|
||||||
//
|
|
||||||
// for item in inputItems {
|
|
||||||
// itemProviders.append(contentsOf: item.attachments ?? [])
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// let _textProvider = itemProviders.first { provider in
|
|
||||||
// return provider.hasRepresentationConforming(toTypeIdentifier: UTType.plainText.identifier, fileOptions: [])
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// let _urlProvider = itemProviders.first { provider in
|
|
||||||
// return provider.hasRepresentationConforming(toTypeIdentifier: UTType.url.identifier, fileOptions: [])
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// let _movieProvider = itemProviders.first { provider in
|
|
||||||
// return provider.hasRepresentationConforming(toTypeIdentifier: UTType.movie.identifier, fileOptions: [])
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// let imageProviders = itemProviders.filter { provider in
|
|
||||||
// return provider.hasRepresentationConforming(toTypeIdentifier: UTType.image.identifier, fileOptions: [])
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// Task { @MainActor in
|
|
||||||
// async let text = ShareViewModel.loadText(textProvider: _textProvider)
|
|
||||||
// async let url = ShareViewModel.loadURL(textProvider: _urlProvider)
|
|
||||||
//
|
|
||||||
// let content = await [text, url]
|
|
||||||
// .compactMap { $0 }
|
|
||||||
// .joined(separator: " ")
|
|
||||||
// self.composeViewModel.statusContent = content
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// guard let api = self.api else { return }
|
|
||||||
//
|
|
||||||
// if let movieProvider = _movieProvider {
|
|
||||||
// composeViewModel.setupAttachmentViewModels([
|
|
||||||
// StatusAttachmentViewModel(api: api, itemProvider: movieProvider)
|
|
||||||
// ])
|
|
||||||
// } else if !imageProviders.isEmpty {
|
|
||||||
// let viewModels = imageProviders.map { provider in
|
|
||||||
// StatusAttachmentViewModel(api: api, itemProvider: provider)
|
|
||||||
// }
|
|
||||||
// composeViewModel.setupAttachmentViewModels(viewModels)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// private static func loadText(textProvider: NSItemProvider?) async -> String? {
|
|
||||||
// guard let textProvider = textProvider else { return nil }
|
|
||||||
// do {
|
|
||||||
// let item = try await textProvider.loadItem(forTypeIdentifier: UTType.plainText.identifier)
|
|
||||||
// guard let text = item as? String else { return nil }
|
|
||||||
// return text
|
|
||||||
// } catch {
|
|
||||||
// return nil
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// private static func loadURL(textProvider: NSItemProvider?) async -> String? {
|
|
||||||
// guard let textProvider = textProvider else { return nil }
|
|
||||||
// do {
|
|
||||||
// let item = try await textProvider.loadItem(forTypeIdentifier: UTType.url.identifier)
|
|
||||||
// guard let url = item as? URL else { return nil }
|
|
||||||
// return url.absoluteString
|
|
||||||
// } catch {
|
|
||||||
// return nil
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
//}
|
|
||||||
//
|
|
||||||
//extension ShareViewModel {
|
|
||||||
// func publish() -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Status>, Error> {
|
|
||||||
// guard let authentication = composeViewModel.authentication,
|
|
||||||
// let api = self.api
|
|
||||||
// else {
|
|
||||||
// return Fail(error: APIService.APIError.implicit(.authenticationMissing)).eraseToAnyPublisher()
|
|
||||||
// }
|
|
||||||
// let authenticationBox = MastodonAuthenticationBox(
|
|
||||||
// authenticationRecord: .init(objectID: authentication.objectID),
|
|
||||||
// domain: authentication.domain,
|
|
||||||
// userID: authentication.userID,
|
|
||||||
// appAuthorization: Mastodon.API.OAuth.Authorization(accessToken: authentication.appAccessToken),
|
|
||||||
// userAuthorization: Mastodon.API.OAuth.Authorization(accessToken: authentication.userAccessToken)
|
|
||||||
// )
|
|
||||||
//
|
|
||||||
// let domain = authentication.domain
|
|
||||||
// let attachmentViewModels = composeViewModel.attachmentViewModels
|
|
||||||
// let mediaIDs = attachmentViewModels.compactMap { viewModel in
|
|
||||||
// viewModel.attachment.value?.id
|
|
||||||
// }
|
|
||||||
// let sensitive: Bool = composeViewModel.isContentWarningComposing
|
|
||||||
// let spoilerText: String? = {
|
|
||||||
// let text = composeViewModel.contentWarningContent
|
|
||||||
// guard !text.isEmpty else { return nil }
|
|
||||||
// return text
|
|
||||||
// }()
|
|
||||||
// let visibility = selectedStatusVisibility.value.visibility
|
|
||||||
//
|
|
||||||
// let updateMediaQuerySubscriptions: [AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Attachment>, Error>] = {
|
|
||||||
// var subscriptions: [AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Attachment>, Error>] = []
|
|
||||||
// for attachmentViewModel in attachmentViewModels {
|
|
||||||
// guard let attachmentID = attachmentViewModel.attachment.value?.id else { continue }
|
|
||||||
// let description = attachmentViewModel.descriptionContent.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
||||||
// guard !description.isEmpty else { continue }
|
|
||||||
// let query = Mastodon.API.Media.UpdateMediaQuery(
|
|
||||||
// file: nil,
|
|
||||||
// thumbnail: nil,
|
|
||||||
// description: description,
|
|
||||||
// focus: nil
|
|
||||||
// )
|
|
||||||
// let subscription = api.updateMedia(
|
|
||||||
// domain: domain,
|
|
||||||
// attachmentID: attachmentID,
|
|
||||||
// query: query,
|
|
||||||
// mastodonAuthenticationBox: authenticationBox
|
|
||||||
// )
|
|
||||||
// subscriptions.append(subscription)
|
|
||||||
// }
|
|
||||||
// return subscriptions
|
|
||||||
// }()
|
|
||||||
//
|
|
||||||
// let status = composeViewModel.statusContent
|
|
||||||
//
|
|
||||||
// return Publishers.MergeMany(updateMediaQuerySubscriptions)
|
|
||||||
// .collect()
|
|
||||||
// .asyncMap { attachments in
|
|
||||||
// let query = Mastodon.API.Statuses.PublishStatusQuery(
|
|
||||||
// status: status,
|
|
||||||
// mediaIDs: mediaIDs.isEmpty ? nil : mediaIDs,
|
|
||||||
// pollOptions: nil,
|
|
||||||
// pollExpiresIn: nil,
|
|
||||||
// inReplyToID: nil,
|
|
||||||
// sensitive: sensitive,
|
|
||||||
// spoilerText: spoilerText,
|
|
||||||
// visibility: visibility
|
|
||||||
// )
|
|
||||||
// return try await api.publishStatus(
|
|
||||||
// domain: domain,
|
|
||||||
// idempotencyKey: nil, // FIXME:
|
|
||||||
// query: query,
|
|
||||||
// authenticationBox: authenticationBox
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
// .eraseToAnyPublisher()
|
|
||||||
// }
|
|
||||||
//}
|
|
|
@ -0,0 +1,330 @@
|
||||||
|
//
|
||||||
|
// ShareViewController.swift
|
||||||
|
// ShareActionExtension
|
||||||
|
//
|
||||||
|
// Created by MainasuK on 2022/11/13.
|
||||||
|
//
|
||||||
|
|
||||||
|
import os.log
|
||||||
|
import UIKit
|
||||||
|
import Combine
|
||||||
|
import CoreDataStack
|
||||||
|
import MastodonCore
|
||||||
|
import MastodonUI
|
||||||
|
import MastodonAsset
|
||||||
|
import MastodonLocalization
|
||||||
|
import UniformTypeIdentifiers
|
||||||
|
|
||||||
|
final class ShareViewController: UIViewController {
|
||||||
|
|
||||||
|
let logger = Logger(subsystem: "ShareViewController", category: "ViewController")
|
||||||
|
|
||||||
|
var disposeBag = Set<AnyCancellable>()
|
||||||
|
|
||||||
|
let context = AppContext()
|
||||||
|
private(set) lazy var viewModel = ShareViewModel(context: context)
|
||||||
|
|
||||||
|
let publishButton: UIButton = {
|
||||||
|
let button = RoundedEdgesButton(type: .custom)
|
||||||
|
button.cornerRadius = 10
|
||||||
|
button.contentEdgeInsets = UIEdgeInsets(top: 6, left: 16, bottom: 5, right: 16) // set 28pt height
|
||||||
|
button.titleLabel?.font = .systemFont(ofSize: 14, weight: .bold)
|
||||||
|
button.setTitle(L10n.Scene.Compose.composeAction, for: .normal)
|
||||||
|
return button
|
||||||
|
}()
|
||||||
|
private func configurePublishButtonApperance() {
|
||||||
|
publishButton.adjustsImageWhenHighlighted = false
|
||||||
|
publishButton.setBackgroundImage(.placeholder(color: Asset.Colors.Label.primary.color), for: .normal)
|
||||||
|
publishButton.setBackgroundImage(.placeholder(color: Asset.Colors.Label.primary.color.withAlphaComponent(0.5)), for: .highlighted)
|
||||||
|
publishButton.setBackgroundImage(.placeholder(color: Asset.Colors.Button.disabled.color), for: .disabled)
|
||||||
|
publishButton.setTitleColor(Asset.Colors.Label.primaryReverse.color, for: .normal)
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
publishButton.addTarget(self, action: #selector(ShareViewController.publishBarButtonItemPressed(_:)), for: .touchUpInside)
|
||||||
|
return barButtonItem
|
||||||
|
}()
|
||||||
|
let activityIndicatorBarButtonItem: UIBarButtonItem = {
|
||||||
|
let indicatorView = UIActivityIndicatorView(style: .medium)
|
||||||
|
let barButtonItem = UIBarButtonItem(customView: indicatorView)
|
||||||
|
indicatorView.startAnimating()
|
||||||
|
return barButtonItem
|
||||||
|
}()
|
||||||
|
|
||||||
|
private var composeContentViewModel: ComposeContentViewModel?
|
||||||
|
private var composeContentViewController: ComposeContentViewController?
|
||||||
|
|
||||||
|
let notSignInLabel: UILabel = {
|
||||||
|
let label = UILabel()
|
||||||
|
label.font = .preferredFont(forTextStyle: .subheadline)
|
||||||
|
label.textColor = .secondaryLabel
|
||||||
|
label.text = "No Available Account" // TODO: i18n
|
||||||
|
return label
|
||||||
|
}()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ShareViewController {
|
||||||
|
override func viewDidLoad() {
|
||||||
|
super.viewDidLoad()
|
||||||
|
|
||||||
|
setupTheme(theme: ThemeService.shared.currentTheme.value)
|
||||||
|
ThemeService.shared.apply(theme: ThemeService.shared.currentTheme.value)
|
||||||
|
ThemeService.shared.currentTheme
|
||||||
|
.receive(on: DispatchQueue.main)
|
||||||
|
.sink { [weak self] theme in
|
||||||
|
guard let self = self else { return }
|
||||||
|
self.setupTheme(theme: theme)
|
||||||
|
}
|
||||||
|
.store(in: &disposeBag)
|
||||||
|
|
||||||
|
view.backgroundColor = .systemBackground
|
||||||
|
title = L10n.Scene.Compose.Title.newPost
|
||||||
|
|
||||||
|
navigationItem.leftBarButtonItem = cancelBarButtonItem
|
||||||
|
navigationItem.rightBarButtonItem = publishBarButtonItem
|
||||||
|
|
||||||
|
do {
|
||||||
|
guard let authContext = try setupAuthContext() else {
|
||||||
|
setupHintLabel()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
viewModel.authContext = authContext
|
||||||
|
let composeContentViewModel = ComposeContentViewModel(
|
||||||
|
context: context,
|
||||||
|
authContext: authContext,
|
||||||
|
kind: .post
|
||||||
|
)
|
||||||
|
let composeContentViewController = ComposeContentViewController()
|
||||||
|
composeContentViewController.viewModel = composeContentViewModel
|
||||||
|
addChild(composeContentViewController)
|
||||||
|
composeContentViewController.view.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
view.addSubview(composeContentViewController.view)
|
||||||
|
NSLayoutConstraint.activate([
|
||||||
|
composeContentViewController.view.topAnchor.constraint(equalTo: view.topAnchor),
|
||||||
|
composeContentViewController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor),
|
||||||
|
composeContentViewController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor),
|
||||||
|
composeContentViewController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor),
|
||||||
|
])
|
||||||
|
composeContentViewController.didMove(toParent: self)
|
||||||
|
|
||||||
|
self.composeContentViewModel = composeContentViewModel
|
||||||
|
self.composeContentViewController = composeContentViewController
|
||||||
|
|
||||||
|
Task { @MainActor in
|
||||||
|
let inputItems = self.extensionContext?.inputItems.compactMap { $0 as? NSExtensionItem } ?? []
|
||||||
|
await load(inputItems: inputItems)
|
||||||
|
} // end Task
|
||||||
|
} catch {
|
||||||
|
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): error: \(error.localizedDescription)")
|
||||||
|
}
|
||||||
|
|
||||||
|
viewModel.$isPublishing
|
||||||
|
.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)
|
||||||
|
}
|
||||||
|
|
||||||
|
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
|
||||||
|
super.traitCollectionDidChange(previousTraitCollection)
|
||||||
|
|
||||||
|
configurePublishButtonApperance()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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: NSError(domain: "org.joinmastodon.app.ShareActionExtension", code: -1))
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc private func publishBarButtonItemPressed(_ sender: UIBarButtonItem) {
|
||||||
|
logger.debug("\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public)")
|
||||||
|
|
||||||
|
|
||||||
|
Task { @MainActor in
|
||||||
|
viewModel.isPublishing = true
|
||||||
|
do {
|
||||||
|
guard let statusPublisher = try composeContentViewModel?.statusPublisher(),
|
||||||
|
let authContext = viewModel.authContext
|
||||||
|
else {
|
||||||
|
throw AppError.badRequest
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = try await statusPublisher.publish(api: context.apiService, authContext: authContext)
|
||||||
|
|
||||||
|
self.publishButton.setTitle(L10n.Common.Controls.Actions.done, for: .normal)
|
||||||
|
try await Task.sleep(nanoseconds: 1 * .second)
|
||||||
|
|
||||||
|
self.extensionContext?.completeRequest(returningItems: nil, completionHandler: nil)
|
||||||
|
|
||||||
|
} catch {
|
||||||
|
let alertController = UIAlertController.standardAlert(of: error)
|
||||||
|
present(alertController, animated: true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
viewModel.isPublishing = false
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ShareViewController {
|
||||||
|
private func setupAuthContext() throws -> AuthContext? {
|
||||||
|
let request = MastodonAuthentication.activeSortedFetchRequest // use active order
|
||||||
|
let _authentication = try context.managedObjectContext.fetch(request).first
|
||||||
|
let _authContext = _authentication.flatMap { AuthContext(authentication: $0) }
|
||||||
|
return _authContext
|
||||||
|
}
|
||||||
|
|
||||||
|
private func setupHintLabel() {
|
||||||
|
notSignInLabel.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
view.addSubview(notSignInLabel)
|
||||||
|
NSLayoutConstraint.activate([
|
||||||
|
notSignInLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor),
|
||||||
|
notSignInLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
private func setupTheme(theme: Theme) {
|
||||||
|
view.backgroundColor = theme.systemElevatedBackgroundColor
|
||||||
|
|
||||||
|
let barAppearance = UINavigationBarAppearance()
|
||||||
|
barAppearance.configureWithDefaultBackground()
|
||||||
|
barAppearance.backgroundColor = theme.navigationBarBackgroundColor
|
||||||
|
navigationItem.standardAppearance = barAppearance
|
||||||
|
navigationItem.compactAppearance = barAppearance
|
||||||
|
navigationItem.scrollEdgeAppearance = barAppearance
|
||||||
|
}
|
||||||
|
|
||||||
|
private func showDismissConfirmAlertController() {
|
||||||
|
let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) // can not use alert in extension
|
||||||
|
let discardAction = UIAlertAction(title: L10n.Common.Controls.Actions.discard, style: .destructive) { _ in
|
||||||
|
self.extensionContext?.cancelRequest(withError: ShareError.userCancelShare)
|
||||||
|
}
|
||||||
|
alertController.addAction(discardAction)
|
||||||
|
let okAction = UIAlertAction(title: L10n.Common.Controls.Actions.ok, style: .cancel, handler: nil)
|
||||||
|
alertController.addAction(okAction)
|
||||||
|
self.present(alertController, animated: true, completion: nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - UIAdaptivePresentationControllerDelegate
|
||||||
|
extension ShareViewController: UIAdaptivePresentationControllerDelegate {
|
||||||
|
|
||||||
|
func presentationControllerShouldDismiss(_ presentationController: UIPresentationController) -> Bool {
|
||||||
|
return composeContentViewModel?.shouldDismiss ?? true
|
||||||
|
}
|
||||||
|
|
||||||
|
func presentationControllerDidAttemptToDismiss(_ presentationController: UIPresentationController) {
|
||||||
|
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
|
||||||
|
showDismissConfirmAlertController()
|
||||||
|
}
|
||||||
|
|
||||||
|
func presentationControllerDidDismiss(_ presentationController: UIPresentationController) {
|
||||||
|
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ShareViewController {
|
||||||
|
|
||||||
|
private func load(inputItems: [NSExtensionItem]) async {
|
||||||
|
guard let composeContentViewModel = self.composeContentViewModel,
|
||||||
|
let authContext = viewModel.authContext
|
||||||
|
else {
|
||||||
|
assertionFailure()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var itemProviders: [NSItemProvider] = []
|
||||||
|
|
||||||
|
for item in inputItems {
|
||||||
|
itemProviders.append(contentsOf: item.attachments ?? [])
|
||||||
|
}
|
||||||
|
|
||||||
|
let _textProvider = itemProviders.first { provider in
|
||||||
|
return provider.hasRepresentationConforming(toTypeIdentifier: UTType.plainText.identifier, fileOptions: [])
|
||||||
|
}
|
||||||
|
|
||||||
|
let _urlProvider = itemProviders.first { provider in
|
||||||
|
return provider.hasRepresentationConforming(toTypeIdentifier: UTType.url.identifier, fileOptions: [])
|
||||||
|
}
|
||||||
|
|
||||||
|
let _movieProvider = itemProviders.first { provider in
|
||||||
|
return provider.hasRepresentationConforming(toTypeIdentifier: UTType.movie.identifier, fileOptions: [])
|
||||||
|
}
|
||||||
|
|
||||||
|
let imageProviders = itemProviders.filter { provider in
|
||||||
|
return provider.hasRepresentationConforming(toTypeIdentifier: UTType.image.identifier, fileOptions: [])
|
||||||
|
}
|
||||||
|
|
||||||
|
async let text = ShareViewController.loadText(textProvider: _textProvider)
|
||||||
|
async let url = ShareViewController.loadURL(textProvider: _urlProvider)
|
||||||
|
|
||||||
|
let content = await [text, url]
|
||||||
|
.compactMap { $0 }
|
||||||
|
.joined(separator: " ")
|
||||||
|
// passby the viewModel `content` value
|
||||||
|
if !content.isEmpty {
|
||||||
|
composeContentViewModel.content = content + " "
|
||||||
|
composeContentViewModel.contentMetaText?.textView.insertText(content + " ")
|
||||||
|
}
|
||||||
|
|
||||||
|
if let movieProvider = _movieProvider {
|
||||||
|
let attachmentViewModel = AttachmentViewModel(
|
||||||
|
api: context.apiService,
|
||||||
|
authContext: authContext,
|
||||||
|
input: .itemProvider(movieProvider),
|
||||||
|
delegate: composeContentViewModel
|
||||||
|
)
|
||||||
|
composeContentViewModel.attachmentViewModels.append(attachmentViewModel)
|
||||||
|
} else if !imageProviders.isEmpty {
|
||||||
|
let attachmentViewModels = imageProviders.map { provider in
|
||||||
|
AttachmentViewModel(
|
||||||
|
api: context.apiService,
|
||||||
|
authContext: authContext,
|
||||||
|
input: .itemProvider(provider),
|
||||||
|
delegate: composeContentViewModel
|
||||||
|
)
|
||||||
|
}
|
||||||
|
composeContentViewModel.attachmentViewModels.append(contentsOf: attachmentViewModels)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func loadText(textProvider: NSItemProvider?) async -> String? {
|
||||||
|
guard let textProvider = textProvider else { return nil }
|
||||||
|
do {
|
||||||
|
let item = try await textProvider.loadItem(forTypeIdentifier: UTType.plainText.identifier)
|
||||||
|
guard let text = item as? String else { return nil }
|
||||||
|
return text
|
||||||
|
} catch {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func loadURL(textProvider: NSItemProvider?) async -> String? {
|
||||||
|
guard let textProvider = textProvider else { return nil }
|
||||||
|
do {
|
||||||
|
let item = try await textProvider.loadItem(forTypeIdentifier: UTType.url.identifier)
|
||||||
|
guard let url = item as? URL else { return nil }
|
||||||
|
return url.absoluteString
|
||||||
|
} catch {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ShareViewController {
|
||||||
|
enum ShareError: Error {
|
||||||
|
case `internal`(error: Error)
|
||||||
|
case userCancelShare
|
||||||
|
case missingAuthentication
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
//
|
||||||
|
// ShareViewModel.swift
|
||||||
|
// MastodonShareAction
|
||||||
|
//
|
||||||
|
// Created by MainasuK Cirno on 2021-7-16.
|
||||||
|
//
|
||||||
|
|
||||||
|
import os.log
|
||||||
|
import Foundation
|
||||||
|
import Combine
|
||||||
|
import CoreData
|
||||||
|
import CoreDataStack
|
||||||
|
import MastodonSDK
|
||||||
|
import SwiftUI
|
||||||
|
import UniformTypeIdentifiers
|
||||||
|
import MastodonAsset
|
||||||
|
import MastodonLocalization
|
||||||
|
import MastodonUI
|
||||||
|
import MastodonCore
|
||||||
|
|
||||||
|
final class ShareViewModel {
|
||||||
|
|
||||||
|
let logger = Logger(subsystem: "ComposeViewModel", category: "ViewModel")
|
||||||
|
|
||||||
|
var disposeBag = Set<AnyCancellable>()
|
||||||
|
|
||||||
|
// input
|
||||||
|
let context: AppContext
|
||||||
|
@Published var authContext: AuthContext?
|
||||||
|
|
||||||
|
@Published var isPublishing = false
|
||||||
|
|
||||||
|
// output
|
||||||
|
|
||||||
|
init(
|
||||||
|
context: AppContext
|
||||||
|
) {
|
||||||
|
self.context = context
|
||||||
|
// end init
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue