diff --git a/Packages/ComposeUI/Sources/ComposeUI/ComposeInput.swift b/Packages/ComposeUI/Sources/ComposeUI/ComposeInput.swift index b1d42f03..375fabdb 100644 --- a/Packages/ComposeUI/Sources/ComposeUI/ComposeInput.swift +++ b/Packages/ComposeUI/Sources/ComposeUI/ComposeInput.swift @@ -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) } } diff --git a/Packages/ComposeUI/Sources/ComposeUI/Views/AttachmentsListView.swift b/Packages/ComposeUI/Sources/ComposeUI/Views/AttachmentsListView.swift index 18093230..a42d2c66 100644 --- a/Packages/ComposeUI/Sources/ComposeUI/Views/AttachmentsListView.swift +++ b/Packages/ComposeUI/Sources/ComposeUI/Views/AttachmentsListView.swift @@ -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: 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] diff --git a/Packages/ComposeUI/Sources/ComposeUI/Views/ComposeToolbarView.swift b/Packages/ComposeUI/Sources/ComposeUI/Views/ComposeToolbarView.swift index e87a292f..90929f6b 100644 --- a/Packages/ComposeUI/Sources/ComposeUI/Views/ComposeToolbarView.swift +++ b/Packages/ComposeUI/Sources/ComposeUI/Views/ComposeToolbarView.swift @@ -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: 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 diff --git a/Packages/ComposeUI/Sources/ComposeUI/Views/ComposeView.swift b/Packages/ComposeUI/Sources/ComposeUI/Views/ComposeView.swift index ee40a87b..e3ae532c 100644 --- a/Packages/ComposeUI/Sources/ComposeUI/Views/ComposeView.swift +++ b/Packages/ComposeUI/Sources/ComposeUI/Views/ComposeView.swift @@ -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 diff --git a/Packages/ComposeUI/Sources/ComposeUI/Views/NewMainTextView.swift b/Packages/ComposeUI/Sources/ComposeUI/Views/NewMainTextView.swift index 3286ab77..49c998c8 100644 --- a/Packages/ComposeUI/Sources/ComposeUI/Views/NewMainTextView.swift +++ b/Packages/ComposeUI/Sources/ComposeUI/Views/NewMainTextView.swift @@ -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 { .constant(nil) } -} - -@available(iOS, obsoleted: 16.0) -private extension EnvironmentValues { - var textViewContentHeight: Binding { - get { self[TextViewContentHeightKey.self] } - set { self[TextViewContentHeightKey.self] = newValue } - } -} -#endif