Remove pre-iOS 16 code

This commit is contained in:
Shadowfacts 2024-09-12 15:57:05 -04:00
parent 8243e06e95
commit cad074bcc3
5 changed files with 26 additions and 282 deletions

View File

@ -30,20 +30,14 @@ enum ToolbarElement {
} }
private struct FocusedComposeInput: FocusedValueKey { private struct FocusedComposeInput: FocusedValueKey {
typealias Value = (any ComposeInput)? typealias Value = any ComposeInput
} }
extension FocusedValues { extension FocusedValues {
// This double optional is unfortunate, but avoiding it requires iOS 16 API var composeInput: (any ComposeInput)? {
fileprivate var _composeInput: (any ComposeInput)?? {
get { self[FocusedComposeInput.self] } get { self[FocusedComposeInput.self] }
set { self[FocusedComposeInput.self] = newValue } set { self[FocusedComposeInput.self] = newValue }
} }
var composeInput: (any ComposeInput)? {
get { _composeInput ?? nil }
set { _composeInput = newValue }
}
} }
@propertyWrapper @propertyWrapper
@ -72,6 +66,6 @@ struct FocusedInputModifier: ViewModifier {
func body(content: Content) -> some View { func body(content: Content) -> some View {
content content
.environment(\.composeInputBox, box) .environment(\.composeInputBox, box)
.focusedValue(\._composeInput, box.wrappedValue) .focusedValue(\.composeInput, box.wrappedValue)
} }
} }

View File

@ -28,93 +28,12 @@ struct AttachmentsListView: View {
} }
var body: some View { var body: some View {
if #available(iOS 16.0, *) {
WrappedCollectionView(attachments: draft.draftAttachments, hasPoll: draft.poll != nil, 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 // 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. // view from laying out, and leaving the intrinsic content size at zero too.
.frame(minHeight: 50) .frame(minHeight: 50)
.padding(.horizontal, -8) .padding(.horizontal, -8)
} else {
LegacyAttachmentsList(draft: draft, callbacks: callbacks, canAddAttachment: canAddAttachment)
} }
}
}
@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 { private struct Callbacks {
@ -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, *) @available(iOS 16.0, *)
private struct WrappedCollectionView: UIViewRepresentable { private struct WrappedCollectionView: UIViewRepresentable {
let attachments: [DraftAttachment] let attachments: [DraftAttachment]

View File

@ -41,7 +41,7 @@ struct ComposeToolbarView: View {
VisibilityButton(draft: draft, instanceFeatures: mastodonController.instanceFeatures) VisibilityButton(draft: draft, instanceFeatures: mastodonController.instanceFeatures)
LocalOnlyButton(enabled: $draft.contentWarningEnabled, mastodonController: mastodonController) LocalOnlyButton(enabled: $draft.localOnly, mastodonController: mastodonController)
InsertEmojiButton() InsertEmojiButton()
@ -74,7 +74,7 @@ private struct ToolbarScrollView<Content: View>: View {
} }
} }
} }
.scrollDisabledIfAvailable(realWidth ?? 0 <= minWidth ?? 0) .scrollDisabled(realWidth ?? 0 <= minWidth ?? 0)
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)
.background { .background {
GeometryReader { proxy in GeometryReader { proxy in
@ -246,8 +246,7 @@ private struct LangaugeButton: View {
@State private var hasChanged = false @State private var hasChanged = false
var body: some View { var body: some View {
if #available(iOS 16.0, *), if instanceFeatures.createStatusWithLanguage {
instanceFeatures.createStatusWithLanguage {
LanguagePicker(draftLanguage: $draft.language, hasChangedSelection: $hasChanged) LanguagePicker(draftLanguage: $draft.language, hasChangedSelection: $hasChanged)
.onReceive(NotificationCenter.default.publisher(for: UITextInputMode.currentInputModeDidChangeNotification), perform: currentInputModeChanged) .onReceive(NotificationCenter.default.publisher(for: UITextInputMode.currentInputModeDidChangeNotification), perform: currentInputModeChanged)
.onChange(of: draft.id) { _ in .onChange(of: draft.id) { _ in

View File

@ -15,16 +15,9 @@ struct ComposeView: View {
@EnvironmentObject private var controller: ComposeController @EnvironmentObject private var controller: ComposeController
var body: some View { var body: some View {
if #available(iOS 16.0, *) {
NavigationStack { NavigationStack {
navigationRoot navigationRoot
} }
} else {
NavigationView {
navigationRoot
}
.navigationViewStyle(.stack)
}
} }
private var navigationRoot: some View { private var navigationRoot: some View {
@ -32,7 +25,7 @@ struct ComposeView: View {
ScrollView(.vertical) { ScrollView(.vertical) {
scrollContent scrollContent
} }
.scrollDismissesKeyboardInteractivelyIfAvailable() .scrollDismissesKeyboard(.interactively)
#if !os(visionOS) && !targetEnvironment(macCatalyst) #if !os(visionOS) && !targetEnvironment(macCatalyst)
.modifier(ToolbarSafeAreaInsetModifier()) .modifier(ToolbarSafeAreaInsetModifier())
#endif #endif

View File

@ -18,7 +18,6 @@ struct NewMainTextView: View {
NewMainTextViewRepresentable(value: $value, becomeFirstResponder: $becomeFirstResponder) NewMainTextViewRepresentable(value: $value, becomeFirstResponder: $becomeFirstResponder)
.focused($focusedField, equals: .body) .focused($focusedField, equals: .body)
.modifier(FocusedInputModifier()) .modifier(FocusedInputModifier())
.modifier(HeightExpandingModifier(minHeight: Self.minHeight))
.overlay(alignment: .topLeading) { .overlay(alignment: .topLeading) {
if value.isEmpty { if value.isEmpty {
PlaceholderView() PlaceholderView()
@ -37,18 +36,10 @@ private struct NewMainTextViewRepresentable: UIViewRepresentable {
@Environment(\.composeUIConfig.useTwitterKeyboard) private var useTwitterKeyboard @Environment(\.composeUIConfig.useTwitterKeyboard) private var useTwitterKeyboard
// TODO: test textSelectionStartsAtBeginning // TODO: test textSelectionStartsAtBeginning
@Environment(\.composeUIConfig.textSelectionStartsAtBeginning) private var textSelectionStartsAtBeginning @Environment(\.composeUIConfig.textSelectionStartsAtBeginning) private var textSelectionStartsAtBeginning
#if !os(visionOS)
@Environment(\.textViewContentHeight) @Binding private var textViewContentHeight
#endif
func makeUIView(context: Context) -> UITextView { func makeUIView(context: Context) -> UITextView {
let view: UITextView
// TODO: if we're not doing the pill background, reevaluate whether this version fork is necessary // TODO: if we're not doing the pill background, reevaluate whether this version fork is necessary
if #available(iOS 16.0, *) { let view = WrappedTextView(usingTextLayoutManager: true)
view = WrappedTextView(usingTextLayoutManager: true)
} else {
view = WrappedTextView()
}
view.delegate = context.coordinator view.delegate = context.coordinator
view.adjustsFontForContentSizeCategory = true view.adjustsFontForContentSizeCategory = true
view.textContainer.lineBreakMode = .byWordWrapping view.textContainer.lineBreakMode = .byWordWrapping
@ -92,16 +83,6 @@ private struct NewMainTextViewRepresentable: UIViewRepresentable {
becomeFirstResponder = false 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 { func makeCoordinator() -> WrappedTextViewCoordinator {
@ -149,13 +130,8 @@ private final class WrappedTextViewCoordinator: NSObject {
let str = NSMutableAttributedString(string: text) let str = NSMutableAttributedString(string: text)
let mentionMatches = CharacterCounter.mention.matches(in: text, range: NSRange(location: 0, length: str.length)) let mentionMatches = CharacterCounter.mention.matches(in: text, range: NSRange(location: 0, length: str.length))
for match in mentionMatches.reversed() { for match in mentionMatches.reversed() {
let range: NSRange
if #available(iOS 16.0, *) {
str.insert(NSAttributedString(attachment: Self.attachment), at: match.range.location) str.insert(NSAttributedString(attachment: Self.attachment), at: match.range.location)
range = NSRange(location: match.range.location, length: match.range.length + 1) let range = NSRange(location: match.range.location, length: match.range.length + 1)
} else {
range = match.range
}
str.addAttributes([ str.addAttributes([
.mention: true, .mention: true,
.foregroundColor: UIColor.tintColor, .foregroundColor: UIColor.tintColor,
@ -199,7 +175,6 @@ private final class WrappedTextViewCoordinator: NSObject {
// the attribute range should always be one greater than the match range, to account for the text attachment // 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 { if attribute == nil || attributeRange.length <= match.range.length {
changed = true changed = true
if #available(iOS 16.0, *) {
let newAttributeRange: NSRange let newAttributeRange: NSRange
if attribute == nil { if attribute == nil {
str.insert(NSAttributedString(attachment: Self.attachment), at: match.range.location) str.insert(NSAttributedString(attachment: Self.attachment), at: match.range.location)
@ -211,9 +186,6 @@ private final class WrappedTextViewCoordinator: NSObject {
.mention: true, .mention: true,
.foregroundColor: UIColor.tintColor, .foregroundColor: UIColor.tintColor,
], range: newAttributeRange) ], range: newAttributeRange)
} else {
str.addAttribute(.foregroundColor, value: UIColor.tintColor, range: match.range)
}
} }
} }
@ -276,38 +248,3 @@ private struct PlaceholderView: View {
.allowsHitTesting(false) .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