Compare commits
No commits in common. "24fb0e0e7bf8ecf1927827770c4b9e7246301d54" and "7b218bfd759f415dc695bfafe197f1ef965f489d" have entirely different histories.
24fb0e0e7b
...
7b218bfd75
|
@ -26,6 +26,7 @@ public struct ComposeUIConfig {
|
||||||
// Preferences
|
// Preferences
|
||||||
public var useTwitterKeyboard = false
|
public var useTwitterKeyboard = false
|
||||||
public var contentType = StatusContentType.plain
|
public var contentType = StatusContentType.plain
|
||||||
|
public var automaticallySaveDrafts = false
|
||||||
public var requireAttachmentDescriptions = false
|
public var requireAttachmentDescriptions = false
|
||||||
|
|
||||||
// Host callbacks
|
// Host callbacks
|
||||||
|
|
|
@ -16,13 +16,14 @@ class AttachmentThumbnailController: ViewController {
|
||||||
@Published private var image: UIImage?
|
@Published private var image: UIImage?
|
||||||
@Published private var gifController: GIFController?
|
@Published private var gifController: GIFController?
|
||||||
@Published private var fullSize: Bool = false
|
@Published private var fullSize: Bool = false
|
||||||
|
@Published private var imageBackground: Color?
|
||||||
|
|
||||||
init(attachment: DraftAttachment) {
|
init(attachment: DraftAttachment) {
|
||||||
self.attachment = attachment
|
self.attachment = attachment
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadImageIfNecessary(fullSize: Bool) {
|
func loadImageIfNecessary(fullSize: Bool) {
|
||||||
if (gifController != nil) || (image != nil && self.fullSize) {
|
if (gifController != nil) || (image != nil && fullSize == self.fullSize) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
self.fullSize = fullSize
|
self.fullSize = fullSize
|
||||||
|
|
|
@ -148,11 +148,15 @@ public final class ComposeController: ViewController {
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
func cancel() {
|
func cancel() {
|
||||||
if draft.hasContent {
|
if config.automaticallySaveDrafts {
|
||||||
isShowingSaveDraftSheet = true
|
|
||||||
} else {
|
|
||||||
deleteDraftOnDisappear = true
|
|
||||||
config.dismiss(.cancel)
|
config.dismiss(.cancel)
|
||||||
|
} else {
|
||||||
|
if draft.hasContent {
|
||||||
|
isShowingSaveDraftSheet = true
|
||||||
|
} else {
|
||||||
|
deleteDraftOnDisappear = true
|
||||||
|
config.dismiss(.cancel)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ import UIKit
|
||||||
public protocol DuckableViewController: UIViewController {
|
public protocol DuckableViewController: UIViewController {
|
||||||
var duckableDelegate: DuckableViewControllerDelegate? { get set }
|
var duckableDelegate: DuckableViewControllerDelegate? { get set }
|
||||||
|
|
||||||
func duckableViewControllerShouldDuck() -> DuckAttemptAction
|
func duckableViewControllerShouldDuck() -> Bool
|
||||||
|
|
||||||
func duckableViewControllerMayAttemptToDuck()
|
func duckableViewControllerMayAttemptToDuck()
|
||||||
|
|
||||||
|
@ -20,7 +20,7 @@ public protocol DuckableViewController: UIViewController {
|
||||||
}
|
}
|
||||||
|
|
||||||
extension DuckableViewController {
|
extension DuckableViewController {
|
||||||
public func duckableViewControllerShouldDuck() -> DuckAttemptAction { .duck }
|
public func duckableViewControllerShouldDuck() -> Bool { true }
|
||||||
public func duckableViewControllerMayAttemptToDuck() {}
|
public func duckableViewControllerMayAttemptToDuck() {}
|
||||||
public func duckableViewControllerWillAnimateDuck(withDuration duration: CGFloat, afterDelay delay: CGFloat) {}
|
public func duckableViewControllerWillAnimateDuck(withDuration duration: CGFloat, afterDelay delay: CGFloat) {}
|
||||||
public func duckableViewControllerDidFinishAnimatingDuck() {}
|
public func duckableViewControllerDidFinishAnimatingDuck() {}
|
||||||
|
@ -30,12 +30,6 @@ public protocol DuckableViewControllerDelegate: AnyObject {
|
||||||
func duckableViewControllerWillDismiss(animated: Bool)
|
func duckableViewControllerWillDismiss(animated: Bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum DuckAttemptAction {
|
|
||||||
case duck
|
|
||||||
case dismiss
|
|
||||||
case block
|
|
||||||
}
|
|
||||||
|
|
||||||
extension UIViewController {
|
extension UIViewController {
|
||||||
@available(iOS 16.0, *)
|
@available(iOS 16.0, *)
|
||||||
public func presentDuckable(_ viewController: DuckableViewController, animated: Bool, isDucked: Bool = false) -> Bool {
|
public func presentDuckable(_ viewController: DuckableViewController, animated: Bool, isDucked: Bool = false) -> Bool {
|
||||||
|
|
|
@ -135,18 +135,14 @@ public class DuckableContainerViewController: UIViewController, DuckableViewCont
|
||||||
guard case .presentingDucked(let viewController, isFirstPresentation: _) = state else {
|
guard case .presentingDucked(let viewController, isFirstPresentation: _) = state else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
switch viewController.duckableViewControllerShouldDuck() {
|
guard viewController.duckableViewControllerShouldDuck() else {
|
||||||
case .duck:
|
|
||||||
let placeholder = createPlaceholderForDuckedViewController(viewController)
|
|
||||||
state = .ducked(viewController, placeholder: placeholder)
|
|
||||||
configureChildForDuckedPlaceholder()
|
|
||||||
dismiss(animated: true)
|
|
||||||
case .block:
|
|
||||||
viewController.sheetPresentationController!.selectedDetentIdentifier = .large
|
viewController.sheetPresentationController!.selectedDetentIdentifier = .large
|
||||||
case .dismiss:
|
return
|
||||||
duckableViewControllerWillDismiss(animated: true)
|
|
||||||
dismiss(animated: true)
|
|
||||||
}
|
}
|
||||||
|
let placeholder = createPlaceholderForDuckedViewController(viewController)
|
||||||
|
state = .ducked(viewController, placeholder: placeholder)
|
||||||
|
configureChildForDuckedPlaceholder()
|
||||||
|
dismiss(animated: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func configureChildForDuckedPlaceholder() {
|
private func configureChildForDuckedPlaceholder() {
|
||||||
|
|
|
@ -110,20 +110,18 @@ class MatchedGeometryViewController<Content: View>: UIViewController, UIViewCont
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
|
||||||
func matchedView(id: AnyHashable, source: () -> AnyView) -> some View {
|
func matchedView(id: AnyHashable, source: () -> AnyView) -> some View {
|
||||||
if let frame = state.currentFrames[id],
|
let frame = state.currentFrames[id]!
|
||||||
let dest = state.destinations[id]?.0 {
|
let dest = state.destinations[id]!.0
|
||||||
ZStack {
|
return ZStack {
|
||||||
source()
|
source()
|
||||||
dest
|
dest
|
||||||
.opacity(state.mode == .presenting ? (state.animating ? 1 : 0) : (state.animating ? 0 : 1))
|
.opacity(state.mode == .presenting ? (state.animating ? 1 : 0) : (state.animating ? 0 : 1))
|
||||||
}
|
|
||||||
.frame(width: frame.width, height: frame.height)
|
|
||||||
.position(x: frame.midX, y: frame.midY)
|
|
||||||
.ignoresSafeArea()
|
|
||||||
.animation(.interpolatingSpring(mass: Double(mass), stiffness: Double(state.mode == .presenting ? presentStiffness : dismissStiffness), damping: Double(state.mode == .presenting ? presentDamping : dismissDamping), initialVelocity: 0), value: frame)
|
|
||||||
}
|
}
|
||||||
|
.frame(width: frame.width, height: frame.height)
|
||||||
|
.position(x: frame.midX, y: frame.midY)
|
||||||
|
.ignoresSafeArea()
|
||||||
|
.animation(.interpolatingSpring(mass: Double(mass), stiffness: Double(state.mode == .presenting ? presentStiffness : dismissStiffness), damping: Double(state.mode == .presenting ? presentDamping : dismissDamping), initialVelocity: 0), value: frame)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -152,8 +150,6 @@ class MatchedGeometryPresentationAnimationController<Content: View>: NSObject, U
|
||||||
let container = transitionContext.containerView
|
let container = transitionContext.containerView
|
||||||
|
|
||||||
// add the VC to the container, which kicks off layout out the content hosting controller
|
// add the VC to the container, which kicks off layout out the content hosting controller
|
||||||
matchedGeomVC.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
|
||||||
matchedGeomVC.view.frame = container.bounds
|
|
||||||
container.addSubview(matchedGeomVC.view)
|
container.addSubview(matchedGeomVC.view)
|
||||||
|
|
||||||
// layout out the content hosting controller and having enough destinations may take a while
|
// layout out the content hosting controller and having enough destinations may take a while
|
||||||
|
|
|
@ -63,6 +63,7 @@ public class Preferences: Codable, ObservableObject {
|
||||||
|
|
||||||
self.defaultPostVisibility = try container.decode(Visibility.self, forKey: .defaultPostVisibility)
|
self.defaultPostVisibility = try container.decode(Visibility.self, forKey: .defaultPostVisibility)
|
||||||
self.defaultReplyVisibility = try container.decodeIfPresent(ReplyVisibility.self, forKey: .defaultReplyVisibility) ?? .sameAsPost
|
self.defaultReplyVisibility = try container.decodeIfPresent(ReplyVisibility.self, forKey: .defaultReplyVisibility) ?? .sameAsPost
|
||||||
|
self.automaticallySaveDrafts = try container.decode(Bool.self, forKey: .automaticallySaveDrafts)
|
||||||
self.requireAttachmentDescriptions = try container.decode(Bool.self, forKey: .requireAttachmentDescriptions)
|
self.requireAttachmentDescriptions = try container.decode(Bool.self, forKey: .requireAttachmentDescriptions)
|
||||||
self.contentWarningCopyMode = try container.decode(ContentWarningCopyMode.self, forKey: .contentWarningCopyMode)
|
self.contentWarningCopyMode = try container.decode(ContentWarningCopyMode.self, forKey: .contentWarningCopyMode)
|
||||||
self.mentionReblogger = try container.decode(Bool.self, forKey: .mentionReblogger)
|
self.mentionReblogger = try container.decode(Bool.self, forKey: .mentionReblogger)
|
||||||
|
@ -119,6 +120,7 @@ public class Preferences: Codable, ObservableObject {
|
||||||
|
|
||||||
try container.encode(defaultPostVisibility, forKey: .defaultPostVisibility)
|
try container.encode(defaultPostVisibility, forKey: .defaultPostVisibility)
|
||||||
try container.encode(defaultReplyVisibility, forKey: .defaultReplyVisibility)
|
try container.encode(defaultReplyVisibility, forKey: .defaultReplyVisibility)
|
||||||
|
try container.encode(automaticallySaveDrafts, forKey: .automaticallySaveDrafts)
|
||||||
try container.encode(requireAttachmentDescriptions, forKey: .requireAttachmentDescriptions)
|
try container.encode(requireAttachmentDescriptions, forKey: .requireAttachmentDescriptions)
|
||||||
try container.encode(contentWarningCopyMode, forKey: .contentWarningCopyMode)
|
try container.encode(contentWarningCopyMode, forKey: .contentWarningCopyMode)
|
||||||
try container.encode(mentionReblogger, forKey: .mentionReblogger)
|
try container.encode(mentionReblogger, forKey: .mentionReblogger)
|
||||||
|
@ -170,6 +172,7 @@ public class Preferences: Codable, ObservableObject {
|
||||||
// MARK: Composing
|
// MARK: Composing
|
||||||
@Published public var defaultPostVisibility = Visibility.public
|
@Published public var defaultPostVisibility = Visibility.public
|
||||||
@Published public var defaultReplyVisibility = ReplyVisibility.sameAsPost
|
@Published public var defaultReplyVisibility = ReplyVisibility.sameAsPost
|
||||||
|
@Published public var automaticallySaveDrafts = true
|
||||||
@Published public var requireAttachmentDescriptions = false
|
@Published public var requireAttachmentDescriptions = false
|
||||||
@Published public var contentWarningCopyMode = ContentWarningCopyMode.asIs
|
@Published public var contentWarningCopyMode = ContentWarningCopyMode.asIs
|
||||||
@Published public var mentionReblogger = false
|
@Published public var mentionReblogger = false
|
||||||
|
@ -233,6 +236,7 @@ public class Preferences: Codable, ObservableObject {
|
||||||
|
|
||||||
case defaultPostVisibility
|
case defaultPostVisibility
|
||||||
case defaultReplyVisibility
|
case defaultReplyVisibility
|
||||||
|
case automaticallySaveDrafts
|
||||||
case requireAttachmentDescriptions
|
case requireAttachmentDescriptions
|
||||||
case contentWarningCopyMode
|
case contentWarningCopyMode
|
||||||
case mentionReblogger
|
case mentionReblogger
|
||||||
|
|
|
@ -74,6 +74,8 @@ class ShareHostingController: UIHostingController<ShareHostingController.View> {
|
||||||
var config = ComposeUIConfig()
|
var config = ComposeUIConfig()
|
||||||
config.allowSwitchingDrafts = false
|
config.allowSwitchingDrafts = false
|
||||||
config.textSelectionStartsAtBeginning = true
|
config.textSelectionStartsAtBeginning = true
|
||||||
|
// note: in the share sheet, we ignore this preference
|
||||||
|
config.automaticallySaveDrafts = false
|
||||||
|
|
||||||
config.backgroundColor = Color(uiColor: .appBackground)
|
config.backgroundColor = Color(uiColor: .appBackground)
|
||||||
config.groupedBackgroundColor = Color(uiColor: .appGroupedBackground)
|
config.groupedBackgroundColor = Color(uiColor: .appGroupedBackground)
|
||||||
|
|
|
@ -60,10 +60,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// make sure the persistent container is initialized on the main thread
|
|
||||||
// otherwise initializing it on the background thread can deadlock with accessing it on the main thread elsewhere
|
|
||||||
_ = DraftsPersistentContainer.shared
|
|
||||||
|
|
||||||
DispatchQueue.global(qos: .userInitiated).async {
|
DispatchQueue.global(qos: .userInitiated).async {
|
||||||
let oldDraftsFile = documentsDirectory.appendingPathComponent("drafts").appendingPathExtension("plist")
|
let oldDraftsFile = documentsDirectory.appendingPathComponent("drafts").appendingPathExtension("plist")
|
||||||
let appGroupDraftsFile = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.space.vaccor.Tusker")!.appendingPathComponent("drafts").appendingPathExtension("plist")
|
let appGroupDraftsFile = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.space.vaccor.Tusker")!.appendingPathComponent("drafts").appendingPathExtension("plist")
|
||||||
|
|
|
@ -79,6 +79,7 @@ class ComposeHostingController: UIHostingController<ComposeHostingController.Vie
|
||||||
|
|
||||||
config.useTwitterKeyboard = Preferences.shared.useTwitterKeyboard
|
config.useTwitterKeyboard = Preferences.shared.useTwitterKeyboard
|
||||||
config.contentType = Preferences.shared.statusContentType
|
config.contentType = Preferences.shared.statusContentType
|
||||||
|
config.automaticallySaveDrafts = Preferences.shared.automaticallySaveDrafts
|
||||||
config.requireAttachmentDescriptions = Preferences.shared.requireAttachmentDescriptions
|
config.requireAttachmentDescriptions = Preferences.shared.requireAttachmentDescriptions
|
||||||
|
|
||||||
config.dismiss = { [unowned self] in self.dismiss(mode: $0) }
|
config.dismiss = { [unowned self] in self.dismiss(mode: $0) }
|
||||||
|
@ -137,14 +138,6 @@ class ComposeHostingController: UIHostingController<ComposeHostingController.Vie
|
||||||
|
|
||||||
// MARK: Duckable
|
// MARK: Duckable
|
||||||
|
|
||||||
func duckableViewControllerShouldDuck() -> DuckAttemptAction {
|
|
||||||
if controller.draft.hasContent {
|
|
||||||
return .duck
|
|
||||||
} else {
|
|
||||||
return .dismiss
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func duckableViewControllerWillAnimateDuck(withDuration duration: CGFloat, afterDelay delay: CGFloat) {
|
func duckableViewControllerWillAnimateDuck(withDuration duration: CGFloat, afterDelay delay: CGFloat) {
|
||||||
controller.deleteDraftOnDisappear = false
|
controller.deleteDraftOnDisappear = false
|
||||||
|
|
||||||
|
|
|
@ -58,6 +58,9 @@ struct ComposingPrefsView: View {
|
||||||
|
|
||||||
var composingSection: some View {
|
var composingSection: some View {
|
||||||
Section(header: Text("Composing")) {
|
Section(header: Text("Composing")) {
|
||||||
|
Toggle(isOn: $preferences.automaticallySaveDrafts) {
|
||||||
|
Text("Automatically Save Drafts")
|
||||||
|
}
|
||||||
Toggle(isOn: $preferences.requireAttachmentDescriptions) {
|
Toggle(isOn: $preferences.requireAttachmentDescriptions) {
|
||||||
Text("Require Attachment Descriptions")
|
Text("Require Attachment Descriptions")
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue