Compare commits

..

No commits in common. "24fb0e0e7bf8ecf1927827770c4b9e7246301d54" and "7b218bfd759f415dc695bfafe197f1ef965f489d" have entirely different histories.

11 changed files with 39 additions and 49 deletions

View File

@ -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

View File

@ -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

View File

@ -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)
}
} }
} }

View File

@ -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 {

View File

@ -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() {

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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")

View File

@ -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

View File

@ -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")
} }