Remove pre-iOS 16 code
This commit is contained in:
parent
8243e06e95
commit
cad074bcc3
|
@ -30,20 +30,14 @@ enum ToolbarElement {
|
|||
}
|
||||
|
||||
private struct FocusedComposeInput: FocusedValueKey {
|
||||
typealias Value = (any ComposeInput)?
|
||||
typealias Value = any ComposeInput
|
||||
}
|
||||
|
||||
extension FocusedValues {
|
||||
// This double optional is unfortunate, but avoiding it requires iOS 16 API
|
||||
fileprivate var _composeInput: (any ComposeInput)?? {
|
||||
var composeInput: (any ComposeInput)? {
|
||||
get { self[FocusedComposeInput.self] }
|
||||
set { self[FocusedComposeInput.self] = newValue }
|
||||
}
|
||||
|
||||
var composeInput: (any ComposeInput)? {
|
||||
get { _composeInput ?? nil }
|
||||
set { _composeInput = newValue }
|
||||
}
|
||||
}
|
||||
|
||||
@propertyWrapper
|
||||
|
@ -72,6 +66,6 @@ struct FocusedInputModifier: ViewModifier {
|
|||
func body(content: Content) -> some View {
|
||||
content
|
||||
.environment(\.composeInputBox, box)
|
||||
.focusedValue(\._composeInput, box.wrappedValue)
|
||||
.focusedValue(\.composeInput, box.wrappedValue)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,95 +28,14 @@ struct AttachmentsListView: View {
|
|||
}
|
||||
|
||||
var body: some View {
|
||||
if #available(iOS 16.0, *) {
|
||||
WrappedCollectionView(attachments: draft.draftAttachments, hasPoll: draft.poll != nil, callbacks: callbacks, canAddAttachment: canAddAttachment)
|
||||
// Impose a minimum height, because otherwise it defaults to zero which prevents the collection
|
||||
// view from laying out, and leaving the intrinsic content size at zero too.
|
||||
.frame(minHeight: 50)
|
||||
.padding(.horizontal, -8)
|
||||
} else {
|
||||
LegacyAttachmentsList(draft: draft, callbacks: callbacks, canAddAttachment: canAddAttachment)
|
||||
}
|
||||
WrappedCollectionView(attachments: draft.draftAttachments, hasPoll: draft.poll != nil, callbacks: callbacks, canAddAttachment: canAddAttachment)
|
||||
// Impose a minimum height, because otherwise it defaults to zero which prevents the collection
|
||||
// view from laying out, and leaving the intrinsic content size at zero too.
|
||||
.frame(minHeight: 50)
|
||||
.padding(.horizontal, -8)
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS, obsoleted: 16.0)
|
||||
private struct LegacyAttachmentsList: View {
|
||||
@ObservedObject var draft: Draft
|
||||
let callbacks: Callbacks
|
||||
let canAddAttachment: Bool
|
||||
@State private var attachmentHeights = [NSManagedObjectID: CGFloat]()
|
||||
|
||||
private var totalHeight: CGFloat {
|
||||
let buttonsHeight = 3 * (40 + AttachmentsListPaddingModifier.cellPadding)
|
||||
let rowHeights = draft.attachments.compactMap {
|
||||
attachmentHeights[($0 as! NSManagedObject).objectID]
|
||||
}.reduce(0) { partialResult, height in
|
||||
partialResult + height + AttachmentsListPaddingModifier.cellPadding
|
||||
}
|
||||
return buttonsHeight + rowHeights
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
List {
|
||||
content
|
||||
}
|
||||
.listStyle(.plain)
|
||||
.frame(height: totalHeight)
|
||||
.scrollDisabledIfAvailable(true)
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private var content: some View {
|
||||
ForEach(draft.draftAttachments) { attachment in
|
||||
AttachmentRowView(attachment: attachment)
|
||||
.modifier(AttachmentRowHeightModifier(height: $attachmentHeights[attachment.objectID]))
|
||||
}
|
||||
.onMove(perform: self.moveAttachments)
|
||||
.onDelete(perform: self.removeAttachments)
|
||||
|
||||
AddPhotoButton(canAddAttachment: canAddAttachment, draft: draft, insertAttachments: self.insertAttachments)
|
||||
|
||||
AddDrawingButton(canAddAttachment: canAddAttachment)
|
||||
|
||||
TogglePollButton(poll: draft.poll)
|
||||
}
|
||||
|
||||
// TODO: move this to Callbacks
|
||||
private func insertAttachments(at offset: Int, itemProviders: [NSItemProvider]) {
|
||||
for provider in itemProviders where provider.canLoadObject(ofClass: DraftAttachment.self) {
|
||||
provider.loadObject(ofClass: DraftAttachment.self) { object, error in
|
||||
guard let attachment = object as? DraftAttachment else { return }
|
||||
DispatchQueue.main.async {
|
||||
guard self.canAddAttachment else {
|
||||
return
|
||||
}
|
||||
DraftsPersistentContainer.shared.viewContext.insert(attachment)
|
||||
attachment.draft = self.draft
|
||||
self.draft.attachments.add(attachment)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: move this to Callbacks
|
||||
private func moveAttachments(from source: IndexSet, to destination: Int) {
|
||||
// just using moveObjects(at:to:) on the draft.attachments NSMutableOrderedSet
|
||||
// results in the order switching back to the previous order and then to the correct one
|
||||
// on the subsequent 2 view updates. creating a new set with the proper order doesn't have that problem
|
||||
var array = draft.draftAttachments
|
||||
array.move(fromOffsets: source, toOffset: destination)
|
||||
draft.attachments = NSMutableOrderedSet(array: array)
|
||||
}
|
||||
|
||||
private func removeAttachments(at indices: IndexSet) {
|
||||
for index in indices {
|
||||
callbacks.removeAttachment(at: index)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private struct Callbacks {
|
||||
let draft: Draft
|
||||
let presentAssetPicker: ((@MainActor @escaping ([PHPickerResult]) -> Void) -> Void)?
|
||||
|
@ -157,104 +76,6 @@ private struct Callbacks {
|
|||
}
|
||||
}
|
||||
|
||||
private struct AttachmentRowHeightPreferenceKey: PreferenceKey {
|
||||
static var defaultValue: CGFloat { 0 }
|
||||
static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
|
||||
value = nextValue()
|
||||
}
|
||||
}
|
||||
|
||||
private struct AttachmentRowHeightModifier: ViewModifier {
|
||||
@Binding var height: CGFloat?
|
||||
|
||||
func body(content: Content) -> some View {
|
||||
content
|
||||
.background {
|
||||
GeometryReader { proxy in
|
||||
Color.clear
|
||||
// do the preference dance because onChange(of:inital:_:) is iOS 17+ :/
|
||||
.preference(key: AttachmentRowHeightPreferenceKey.self, value: proxy.size.height)
|
||||
.onPreferenceChange(AttachmentRowHeightPreferenceKey.self) { newValue in
|
||||
height = newValue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct AttachmentsListPaddingModifier: ViewModifier {
|
||||
static let cellPadding: CGFloat = 12
|
||||
|
||||
func body(content: Content) -> some View {
|
||||
content
|
||||
.listRowInsets(EdgeInsets(top: Self.cellPadding / 2, leading: Self.cellPadding / 2, bottom: Self.cellPadding / 2, trailing: Self.cellPadding / 2))
|
||||
}
|
||||
}
|
||||
|
||||
private struct AttachmentsListButton<Label: View>: View {
|
||||
let action: () -> Void
|
||||
@ViewBuilder let label: Label
|
||||
|
||||
var body: some View {
|
||||
Button(action: action) {
|
||||
label
|
||||
}
|
||||
.foregroundStyle(.tint)
|
||||
.frame(height: 40)
|
||||
.modifier(AttachmentsListPaddingModifier())
|
||||
}
|
||||
}
|
||||
|
||||
private struct AddPhotoButton: View {
|
||||
let canAddAttachment: Bool
|
||||
let draft: Draft
|
||||
let insertAttachments: (Int, [NSItemProvider]) -> Void
|
||||
@Environment(\.colorScheme) private var colorScheme
|
||||
@Environment(\.composeUIConfig.presentAssetPicker) private var presentAssetPicker
|
||||
|
||||
var body: some View {
|
||||
AttachmentsListButton {
|
||||
presentAssetPicker?() { results in
|
||||
insertAttachments(draft.attachments.count, results.map(\.itemProvider))
|
||||
}
|
||||
} label: {
|
||||
Label("Add photo or video", systemImage: colorScheme == .dark ? "photo.fill" : "photo")
|
||||
}
|
||||
.disabled(!canAddAttachment)
|
||||
}
|
||||
}
|
||||
|
||||
private struct AddDrawingButton: View {
|
||||
let canAddAttachment: Bool
|
||||
|
||||
var body: some View {
|
||||
AttachmentsListButton {
|
||||
fatalError("TODO")
|
||||
} label: {
|
||||
Label("Draw something", systemImage: "hand.draw")
|
||||
}
|
||||
.disabled(!canAddAttachment)
|
||||
}
|
||||
}
|
||||
|
||||
private struct TogglePollButton: View {
|
||||
let poll: Poll?
|
||||
|
||||
var canAddPoll: Bool {
|
||||
// TODO
|
||||
true
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
AttachmentsListButton {
|
||||
fatalError("TODO")
|
||||
} label: {
|
||||
Label(poll == nil ? "Add a poll" : "Remove poll", systemImage: "chart.bar.doc.horizontal")
|
||||
}
|
||||
.disabled(!canAddPoll)
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 16.0, *)
|
||||
private struct WrappedCollectionView: UIViewRepresentable {
|
||||
let attachments: [DraftAttachment]
|
||||
|
|
|
@ -41,7 +41,7 @@ struct ComposeToolbarView: View {
|
|||
|
||||
VisibilityButton(draft: draft, instanceFeatures: mastodonController.instanceFeatures)
|
||||
|
||||
LocalOnlyButton(enabled: $draft.contentWarningEnabled, mastodonController: mastodonController)
|
||||
LocalOnlyButton(enabled: $draft.localOnly, mastodonController: mastodonController)
|
||||
|
||||
InsertEmojiButton()
|
||||
|
||||
|
@ -74,7 +74,7 @@ private struct ToolbarScrollView<Content: View>: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
.scrollDisabledIfAvailable(realWidth ?? 0 <= minWidth ?? 0)
|
||||
.scrollDisabled(realWidth ?? 0 <= minWidth ?? 0)
|
||||
.frame(maxWidth: .infinity)
|
||||
.background {
|
||||
GeometryReader { proxy in
|
||||
|
@ -246,8 +246,7 @@ private struct LangaugeButton: View {
|
|||
@State private var hasChanged = false
|
||||
|
||||
var body: some View {
|
||||
if #available(iOS 16.0, *),
|
||||
instanceFeatures.createStatusWithLanguage {
|
||||
if instanceFeatures.createStatusWithLanguage {
|
||||
LanguagePicker(draftLanguage: $draft.language, hasChangedSelection: $hasChanged)
|
||||
.onReceive(NotificationCenter.default.publisher(for: UITextInputMode.currentInputModeDidChangeNotification), perform: currentInputModeChanged)
|
||||
.onChange(of: draft.id) { _ in
|
||||
|
|
|
@ -15,15 +15,8 @@ struct ComposeView: View {
|
|||
@EnvironmentObject private var controller: ComposeController
|
||||
|
||||
var body: some View {
|
||||
if #available(iOS 16.0, *) {
|
||||
NavigationStack {
|
||||
navigationRoot
|
||||
}
|
||||
} else {
|
||||
NavigationView {
|
||||
navigationRoot
|
||||
}
|
||||
.navigationViewStyle(.stack)
|
||||
NavigationStack {
|
||||
navigationRoot
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -32,7 +25,7 @@ struct ComposeView: View {
|
|||
ScrollView(.vertical) {
|
||||
scrollContent
|
||||
}
|
||||
.scrollDismissesKeyboardInteractivelyIfAvailable()
|
||||
.scrollDismissesKeyboard(.interactively)
|
||||
#if !os(visionOS) && !targetEnvironment(macCatalyst)
|
||||
.modifier(ToolbarSafeAreaInsetModifier())
|
||||
#endif
|
||||
|
|
|
@ -18,7 +18,6 @@ struct NewMainTextView: View {
|
|||
NewMainTextViewRepresentable(value: $value, becomeFirstResponder: $becomeFirstResponder)
|
||||
.focused($focusedField, equals: .body)
|
||||
.modifier(FocusedInputModifier())
|
||||
.modifier(HeightExpandingModifier(minHeight: Self.minHeight))
|
||||
.overlay(alignment: .topLeading) {
|
||||
if value.isEmpty {
|
||||
PlaceholderView()
|
||||
|
@ -37,18 +36,10 @@ private struct NewMainTextViewRepresentable: UIViewRepresentable {
|
|||
@Environment(\.composeUIConfig.useTwitterKeyboard) private var useTwitterKeyboard
|
||||
// TODO: test textSelectionStartsAtBeginning
|
||||
@Environment(\.composeUIConfig.textSelectionStartsAtBeginning) private var textSelectionStartsAtBeginning
|
||||
#if !os(visionOS)
|
||||
@Environment(\.textViewContentHeight) @Binding private var textViewContentHeight
|
||||
#endif
|
||||
|
||||
func makeUIView(context: Context) -> UITextView {
|
||||
let view: UITextView
|
||||
// TODO: if we're not doing the pill background, reevaluate whether this version fork is necessary
|
||||
if #available(iOS 16.0, *) {
|
||||
view = WrappedTextView(usingTextLayoutManager: true)
|
||||
} else {
|
||||
view = WrappedTextView()
|
||||
}
|
||||
let view = WrappedTextView(usingTextLayoutManager: true)
|
||||
view.delegate = context.coordinator
|
||||
view.adjustsFontForContentSizeCategory = true
|
||||
view.textContainer.lineBreakMode = .byWordWrapping
|
||||
|
@ -92,16 +83,6 @@ private struct NewMainTextViewRepresentable: UIViewRepresentable {
|
|||
becomeFirstResponder = false
|
||||
}
|
||||
}
|
||||
|
||||
#if !os(visionOS)
|
||||
if #unavailable(iOS 16.0) {
|
||||
DispatchQueue.main.async {
|
||||
let targetSize = CGSize(width: uiView.bounds.width, height: UIView.layoutFittingCompressedSize.height)
|
||||
let fittingSize = uiView.systemLayoutSizeFitting(targetSize, withHorizontalFittingPriority: .required, verticalFittingPriority: .defaultHigh)
|
||||
textViewContentHeight = fittingSize.height
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
func makeCoordinator() -> WrappedTextViewCoordinator {
|
||||
|
@ -149,13 +130,8 @@ private final class WrappedTextViewCoordinator: NSObject {
|
|||
let str = NSMutableAttributedString(string: text)
|
||||
let mentionMatches = CharacterCounter.mention.matches(in: text, range: NSRange(location: 0, length: str.length))
|
||||
for match in mentionMatches.reversed() {
|
||||
let range: NSRange
|
||||
if #available(iOS 16.0, *) {
|
||||
str.insert(NSAttributedString(attachment: Self.attachment), at: match.range.location)
|
||||
range = NSRange(location: match.range.location, length: match.range.length + 1)
|
||||
} else {
|
||||
range = match.range
|
||||
}
|
||||
str.insert(NSAttributedString(attachment: Self.attachment), at: match.range.location)
|
||||
let range = NSRange(location: match.range.location, length: match.range.length + 1)
|
||||
str.addAttributes([
|
||||
.mention: true,
|
||||
.foregroundColor: UIColor.tintColor,
|
||||
|
@ -199,21 +175,17 @@ private final class WrappedTextViewCoordinator: NSObject {
|
|||
// the attribute range should always be one greater than the match range, to account for the text attachment
|
||||
if attribute == nil || attributeRange.length <= match.range.length {
|
||||
changed = true
|
||||
if #available(iOS 16.0, *) {
|
||||
let newAttributeRange: NSRange
|
||||
if attribute == nil {
|
||||
str.insert(NSAttributedString(attachment: Self.attachment), at: match.range.location)
|
||||
newAttributeRange = NSRange(location: match.range.location, length: match.range.length + 1)
|
||||
} else {
|
||||
newAttributeRange = match.range
|
||||
}
|
||||
str.addAttributes([
|
||||
.mention: true,
|
||||
.foregroundColor: UIColor.tintColor,
|
||||
], range: newAttributeRange)
|
||||
let newAttributeRange: NSRange
|
||||
if attribute == nil {
|
||||
str.insert(NSAttributedString(attachment: Self.attachment), at: match.range.location)
|
||||
newAttributeRange = NSRange(location: match.range.location, length: match.range.length + 1)
|
||||
} else {
|
||||
str.addAttribute(.foregroundColor, value: UIColor.tintColor, range: match.range)
|
||||
newAttributeRange = match.range
|
||||
}
|
||||
str.addAttributes([
|
||||
.mention: true,
|
||||
.foregroundColor: UIColor.tintColor,
|
||||
], range: newAttributeRange)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -276,38 +248,3 @@ private struct PlaceholderView: View {
|
|||
.allowsHitTesting(false)
|
||||
}
|
||||
}
|
||||
|
||||
#if !os(visionOS)
|
||||
@available(iOS, obsoleted: 16.0)
|
||||
private struct HeightExpandingModifier: ViewModifier {
|
||||
let minHeight: CGFloat
|
||||
|
||||
@State private var height: CGFloat?
|
||||
private var effectiveHeight: CGFloat {
|
||||
height.map { max($0, minHeight) } ?? minHeight
|
||||
}
|
||||
|
||||
func body(content: Content) -> some View {
|
||||
if #available(iOS 16.0, *) {
|
||||
content
|
||||
} else {
|
||||
content
|
||||
.frame(height: effectiveHeight)
|
||||
.environment(\.textViewContentHeight, $height)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS, obsoleted: 16.0)
|
||||
private struct TextViewContentHeightKey: EnvironmentKey {
|
||||
static var defaultValue: Binding<CGFloat?> { .constant(nil) }
|
||||
}
|
||||
|
||||
@available(iOS, obsoleted: 16.0)
|
||||
private extension EnvironmentValues {
|
||||
var textViewContentHeight: Binding<CGFloat?> {
|
||||
get { self[TextViewContentHeightKey.self] }
|
||||
set { self[TextViewContentHeightKey.self] = newValue }
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
Loading…
Reference in New Issue