From 06183f564980bbfdd9fa337a700b3ad3c757f23e Mon Sep 17 00:00:00 2001 From: Jed Fox Date: Tue, 7 Feb 2023 16:52:09 -0500 Subject: [PATCH 1/6] Refactor: remove unnecessary Published property --- .../Scene/ComposeContent/Poll/PollAddOptionRow.swift | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/Poll/PollAddOptionRow.swift b/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/Poll/PollAddOptionRow.swift index 5a8ae58d1..c22b22432 100644 --- a/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/Poll/PollAddOptionRow.swift +++ b/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/Poll/PollAddOptionRow.swift @@ -22,7 +22,7 @@ public struct PollAddOptionRow: View { .padding(.trailing, 16 - 10) // 8pt for TextField leading .font(.system(size: 17)) PollOptionTextField( - text: $viewModel.text, + text: .constant(""), index: 999, delegate: nil ) { textField in @@ -44,9 +44,6 @@ public struct PollAddOptionRow: View { extension PollAddOptionRow { public class ViewModel: ObservableObject { - // input - @Published public var text: String = "" - // output @Published public var backgroundColor = ThemeService.shared.currentTheme.value.composePollRowBackgroundColor From 7d8c40218c5bb20f9922366026ac0bbfbc83b45c Mon Sep 17 00:00:00 2001 From: Jed Fox Date: Tue, 7 Feb 2023 16:55:58 -0500 Subject: [PATCH 2/6] Add move up / move down / remove option actions to poll options --- Localization/app.json | 5 +- .../Generated/Strings.swift | 6 ++ .../Resources/Base.lproj/Localizable.strings | 3 + .../ComposeContent/Poll/PollOptionRow.swift | 64 ++++++++++++++++++- .../View/ComposeContentView.swift | 24 +++++-- 5 files changed, 91 insertions(+), 11 deletions(-) diff --git a/Localization/app.json b/Localization/app.json index 61c4a7fa0..e6219b4a6 100644 --- a/Localization/app.json +++ b/Localization/app.json @@ -470,7 +470,10 @@ "seven_days": "7 Days", "option_number": "Option %ld", "the_poll_is_invalid": "The poll is invalid", - "the_poll_has_empty_option": "The poll has empty option" + "the_poll_has_empty_option": "The poll has empty option", + "remove_option": "Remove Option", + "move_up": "Move Up", + "move_down": "Move Down" }, "content_warning": { "placeholder": "Write an accurate warning here..." diff --git a/MastodonSDK/Sources/MastodonLocalization/Generated/Strings.swift b/MastodonSDK/Sources/MastodonLocalization/Generated/Strings.swift index 200b835e4..c75bb9fd9 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Generated/Strings.swift +++ b/MastodonSDK/Sources/MastodonLocalization/Generated/Strings.swift @@ -597,6 +597,10 @@ public enum L10n { public static func durationTime(_ p1: Any) -> String { return L10n.tr("Localizable", "Scene.Compose.Poll.DurationTime", String(describing: p1), fallback: "Duration: %@") } + /// Move Down + public static let moveDown = L10n.tr("Localizable", "Scene.Compose.Poll.MoveDown", fallback: "Move Down") + /// Move Up + public static let moveUp = L10n.tr("Localizable", "Scene.Compose.Poll.MoveUp", fallback: "Move Up") /// 1 Day public static let oneDay = L10n.tr("Localizable", "Scene.Compose.Poll.OneDay", fallback: "1 Day") /// 1 Hour @@ -605,6 +609,8 @@ public enum L10n { public static func optionNumber(_ p1: Int) -> String { return L10n.tr("Localizable", "Scene.Compose.Poll.OptionNumber", p1, fallback: "Option %ld") } + /// Remove Option + public static let removeOption = L10n.tr("Localizable", "Scene.Compose.Poll.RemoveOption", fallback: "Remove Option") /// 7 Days public static let sevenDays = L10n.tr("Localizable", "Scene.Compose.Poll.SevenDays", fallback: "7 Days") /// 6 Hours diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/Base.lproj/Localizable.strings b/MastodonSDK/Sources/MastodonLocalization/Resources/Base.lproj/Localizable.strings index 0cffc5e14..2f57c40fe 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/Base.lproj/Localizable.strings +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/Base.lproj/Localizable.strings @@ -212,9 +212,12 @@ uploaded to Mastodon."; "Scene.Compose.MediaSelection.Camera" = "Take Photo"; "Scene.Compose.MediaSelection.PhotoLibrary" = "Photo Library"; "Scene.Compose.Poll.DurationTime" = "Duration: %@"; +"Scene.Compose.Poll.MoveDown" = "Move Down"; +"Scene.Compose.Poll.MoveUp" = "Move Up"; "Scene.Compose.Poll.OneDay" = "1 Day"; "Scene.Compose.Poll.OneHour" = "1 Hour"; "Scene.Compose.Poll.OptionNumber" = "Option %ld"; +"Scene.Compose.Poll.RemoveOption" = "Remove Option"; "Scene.Compose.Poll.SevenDays" = "7 Days"; "Scene.Compose.Poll.SixHours" = "6 Hours"; "Scene.Compose.Poll.ThePollHasEmptyOption" = "The poll has empty option"; diff --git a/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/Poll/PollOptionRow.swift b/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/Poll/PollOptionRow.swift index 0cf451d8d..1f03d4203 100644 --- a/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/Poll/PollOptionRow.swift +++ b/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/Poll/PollOptionRow.swift @@ -8,12 +8,16 @@ import SwiftUI import MastodonAsset import MastodonCore +import MastodonLocalization public struct PollOptionRow: View { @ObservedObject var viewModel: PollComposeItem.Option - let index: Int? + let index: Int + let moveUp: (() -> Void)? + let moveDown: (() -> Void)? + let removeOption: (() -> Void)? let deleteBackwardResponseTextFieldRelayDelegate: DeleteBackwardResponseTextFieldRelayDelegate? let configurationHandler: (DeleteBackwardResponseTextField) -> Void @@ -25,9 +29,9 @@ public struct PollOptionRow: View { .padding(.leading, 16) .padding(.trailing, 16 - 10) // 8pt for TextField leading .font(.system(size: 17)) - PollOptionTextField( + let field = PollOptionTextField( text: $viewModel.text, - index: index ?? -1, + index: index, delegate: deleteBackwardResponseTextFieldRelayDelegate ) { textField in viewModel.textField = textField @@ -38,6 +42,60 @@ public struct PollOptionRow: View { viewModel.shouldBecomeFirstResponder = false viewModel.textField?.becomeFirstResponder() } + + if #available(iOS 16.0, *) { + field.accessibilityActions { + if let moveUp { + Button(L10n.Scene.Compose.Poll.moveUp, action: moveUp) + } + if let moveDown { + Button(L10n.Scene.Compose.Poll.moveDown, action: moveDown) + } + if let removeOption { + Button(L10n.Scene.Compose.Poll.removeOption, action: removeOption) + } + } + } else { + // beautiful! + if let moveUp { + if let moveDown { + if let removeOption { + field + .accessibilityAction(named: L10n.Scene.Compose.Poll.moveUp, moveUp) + .accessibilityAction(named: L10n.Scene.Compose.Poll.moveDown, moveDown) + .accessibilityAction(named: L10n.Scene.Compose.Poll.removeOption, removeOption) + } else { + field + .accessibilityAction(named: L10n.Scene.Compose.Poll.moveUp, moveUp) + .accessibilityAction(named: L10n.Scene.Compose.Poll.moveDown, moveDown) + } + } else { + if let removeOption { + field + .accessibilityAction(named: L10n.Scene.Compose.Poll.moveUp, moveUp) + .accessibilityAction(named: L10n.Scene.Compose.Poll.removeOption, removeOption) + } else { + field.accessibilityAction(named: L10n.Scene.Compose.Poll.moveUp, moveUp) + } + } + } else { + if let moveDown { + if let removeOption { + field + .accessibilityAction(named: L10n.Scene.Compose.Poll.moveDown, moveDown) + .accessibilityAction(named: L10n.Scene.Compose.Poll.removeOption, removeOption) + } else { + field.accessibilityAction(named: L10n.Scene.Compose.Poll.moveDown, moveDown) + } + } else { + if let removeOption { + field.accessibilityAction(named: L10n.Scene.Compose.Poll.removeOption, removeOption) + } else { + field + } + } + } + } } .background(Color(viewModel.backgroundColor)) .cornerRadius(10) diff --git a/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/View/ComposeContentView.swift b/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/View/ComposeContentView.swift index 8d06d4c1e..26dec726d 100644 --- a/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/View/ComposeContentView.swift +++ b/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/View/ComposeContentView.swift @@ -180,13 +180,23 @@ extension ComposeContentView { ReorderableForEach( items: $viewModel.pollOptions ) { $pollOption in - let _index = viewModel.pollOptions.firstIndex(of: pollOption) - PollOptionRow( - viewModel: pollOption, - index: _index, - deleteBackwardResponseTextFieldRelayDelegate: viewModel - ) { textField in - viewModel.customEmojiPickerInputViewModel.configure(textInput: textField) + if let _index = viewModel.pollOptions.firstIndex(of: pollOption) { + PollOptionRow( + viewModel: pollOption, + index: _index, + moveUp: _index == 0 ? nil : { + viewModel.pollOptions.swapAt(_index, _index - 1) + }, + moveDown: _index == viewModel.pollOptions.count - 1 ? nil : { + viewModel.pollOptions.swapAt(_index, _index + 1) + }, + removeOption: viewModel.pollOptions.count <= 2 ? nil : { + viewModel.pollOptions.remove(at: _index) + }, + deleteBackwardResponseTextFieldRelayDelegate: viewModel + ) { textField in + viewModel.customEmojiPickerInputViewModel.configure(textInput: textField) + } } } if viewModel.maxPollOptionLimit != viewModel.pollOptions.count { From 5637056790dd6de303f4074212acebd99e362236 Mon Sep 17 00:00:00 2001 From: Jed Fox Date: Tue, 7 Feb 2023 16:56:49 -0500 Subject: [PATCH 3/6] =?UTF-8?q?Add=20label=20to=20=E2=80=9CAdd=20Option?= =?UTF-8?q?=E2=80=9D=20button?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Localization/app.json | 1 + .../Sources/MastodonLocalization/Generated/Strings.swift | 2 ++ .../Resources/Base.lproj/Localizable.strings | 1 + .../Scene/ComposeContent/View/ComposeContentView.swift | 8 ++++---- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/Localization/app.json b/Localization/app.json index e6219b4a6..6d6ad4ac4 100644 --- a/Localization/app.json +++ b/Localization/app.json @@ -471,6 +471,7 @@ "option_number": "Option %ld", "the_poll_is_invalid": "The poll is invalid", "the_poll_has_empty_option": "The poll has empty option", + "add_option": "Add Option", "remove_option": "Remove Option", "move_up": "Move Up", "move_down": "Move Down" diff --git a/MastodonSDK/Sources/MastodonLocalization/Generated/Strings.swift b/MastodonSDK/Sources/MastodonLocalization/Generated/Strings.swift index c75bb9fd9..8768012f3 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Generated/Strings.swift +++ b/MastodonSDK/Sources/MastodonLocalization/Generated/Strings.swift @@ -593,6 +593,8 @@ public enum L10n { public static let photoLibrary = L10n.tr("Localizable", "Scene.Compose.MediaSelection.PhotoLibrary", fallback: "Photo Library") } public enum Poll { + /// Add Option + public static let addOption = L10n.tr("Localizable", "Scene.Compose.Poll.AddOption", fallback: "Add Option") /// Duration: %@ public static func durationTime(_ p1: Any) -> String { return L10n.tr("Localizable", "Scene.Compose.Poll.DurationTime", String(describing: p1), fallback: "Duration: %@") diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/Base.lproj/Localizable.strings b/MastodonSDK/Sources/MastodonLocalization/Resources/Base.lproj/Localizable.strings index 2f57c40fe..d9c9a2926 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/Base.lproj/Localizable.strings +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/Base.lproj/Localizable.strings @@ -211,6 +211,7 @@ uploaded to Mastodon."; "Scene.Compose.MediaSelection.Browse" = "Browse"; "Scene.Compose.MediaSelection.Camera" = "Take Photo"; "Scene.Compose.MediaSelection.PhotoLibrary" = "Photo Library"; +"Scene.Compose.Poll.AddOption" = "Add Option"; "Scene.Compose.Poll.DurationTime" = "Duration: %@"; "Scene.Compose.Poll.MoveDown" = "Move Down"; "Scene.Compose.Poll.MoveUp" = "Move Up"; diff --git a/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/View/ComposeContentView.swift b/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/View/ComposeContentView.swift index 26dec726d..714b751b7 100644 --- a/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/View/ComposeContentView.swift +++ b/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/View/ComposeContentView.swift @@ -200,10 +200,10 @@ extension ComposeContentView { } } if viewModel.maxPollOptionLimit != viewModel.pollOptions.count { - PollAddOptionRow() - .onTapGesture { - viewModel.createNewPollOptionIfCould() - } + Button(action: viewModel.createNewPollOptionIfCould) { + PollAddOptionRow() + .accessibilityLabel(L10n.Scene.Compose.Poll.addOption) + } } Menu { Picker(selection: $viewModel.pollExpireConfigurationOption) { From 9f541957a7bcf56a06663b43acc50ab96ecdd83a Mon Sep 17 00:00:00 2001 From: Jed Fox Date: Tue, 7 Feb 2023 16:57:08 -0500 Subject: [PATCH 4/6] Hide the circle and grabber icons from a11y tools --- .../MastodonUI/Scene/ComposeContent/Poll/PollOptionRow.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/Poll/PollOptionRow.swift b/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/Poll/PollOptionRow.swift index 1f03d4203..852c628bf 100644 --- a/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/Poll/PollOptionRow.swift +++ b/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/Poll/PollOptionRow.swift @@ -29,6 +29,7 @@ public struct PollOptionRow: View { .padding(.leading, 16) .padding(.trailing, 16 - 10) // 8pt for TextField leading .font(.system(size: 17)) + .accessibilityHidden(true) let field = PollOptionTextField( text: $viewModel.text, index: index, @@ -102,6 +103,7 @@ public struct PollOptionRow: View { .shadow(color: .black.opacity(0.3), radius: 2, x: 0, y: 1) Image(uiImage: Asset.Scene.Compose.reorderDot.image.withRenderingMode(.alwaysTemplate)) .foregroundColor(Color(UIColor.label)) + .accessibilityHidden(true) } .background(Color.clear) } From 8217f5ce28df94a9f02ee4348806b95c4a092daf Mon Sep 17 00:00:00 2001 From: Jed Fox Date: Tue, 7 Feb 2023 17:00:22 -0500 Subject: [PATCH 5/6] Mark the poll editor as a group --- Localization/StringsConvertor/input/Base.lproj/app.json | 1 + Localization/app.json | 1 + .../Sources/MastodonLocalization/Generated/Strings.swift | 2 ++ .../Resources/Base.lproj/Localizable.strings | 1 + .../Scene/ComposeContent/View/ComposeContentView.swift | 2 ++ 5 files changed, 7 insertions(+) diff --git a/Localization/StringsConvertor/input/Base.lproj/app.json b/Localization/StringsConvertor/input/Base.lproj/app.json index 47890e24f..8de468601 100644 --- a/Localization/StringsConvertor/input/Base.lproj/app.json +++ b/Localization/StringsConvertor/input/Base.lproj/app.json @@ -444,6 +444,7 @@ "server_processing_state": "Server Processing..." }, "poll": { + "title": "Poll", "duration_time": "Duration: %s", "thirty_minutes": "30 minutes", "one_hour": "1 Hour", diff --git a/Localization/app.json b/Localization/app.json index 6d6ad4ac4..d90c3729e 100644 --- a/Localization/app.json +++ b/Localization/app.json @@ -461,6 +461,7 @@ "server_processing_state": "Server Processing..." }, "poll": { + "title": "Poll", "duration_time": "Duration: %s", "thirty_minutes": "30 minutes", "one_hour": "1 Hour", diff --git a/MastodonSDK/Sources/MastodonLocalization/Generated/Strings.swift b/MastodonSDK/Sources/MastodonLocalization/Generated/Strings.swift index 8768012f3..40b57659d 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Generated/Strings.swift +++ b/MastodonSDK/Sources/MastodonLocalization/Generated/Strings.swift @@ -625,6 +625,8 @@ public enum L10n { public static let thirtyMinutes = L10n.tr("Localizable", "Scene.Compose.Poll.ThirtyMinutes", fallback: "30 minutes") /// 3 Days public static let threeDays = L10n.tr("Localizable", "Scene.Compose.Poll.ThreeDays", fallback: "3 Days") + /// Poll + public static let title = L10n.tr("Localizable", "Scene.Compose.Poll.Title", fallback: "Poll") } public enum Title { /// New Post diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/Base.lproj/Localizable.strings b/MastodonSDK/Sources/MastodonLocalization/Resources/Base.lproj/Localizable.strings index d9c9a2926..4412c3f19 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/Base.lproj/Localizable.strings +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/Base.lproj/Localizable.strings @@ -225,6 +225,7 @@ uploaded to Mastodon."; "Scene.Compose.Poll.ThePollIsInvalid" = "The poll is invalid"; "Scene.Compose.Poll.ThirtyMinutes" = "30 minutes"; "Scene.Compose.Poll.ThreeDays" = "3 Days"; +"Scene.Compose.Poll.Title" = "Poll"; "Scene.Compose.ReplyingToUser" = "replying to %@"; "Scene.Compose.Title.NewPost" = "New Post"; "Scene.Compose.Title.NewReply" = "New Reply"; diff --git a/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/View/ComposeContentView.swift b/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/View/ComposeContentView.swift index 714b751b7..d540985b5 100644 --- a/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/View/ComposeContentView.swift +++ b/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/View/ComposeContentView.swift @@ -224,6 +224,8 @@ extension ComposeContentView { } } } // end VStack + .accessibilityElement(children: .contain) + .accessibilityLabel(L10n.Scene.Compose.Poll.title) } // MARK: - media From b178c0cff9bd9d50c8c8b4788bc3a24d8d33f61b Mon Sep 17 00:00:00 2001 From: Marcus Kida Date: Wed, 8 Feb 2023 15:22:23 +0100 Subject: [PATCH 6/6] Refactor if/else in PollOptionRow --- .../ComposeContent/Poll/PollOptionRow.swift | 64 ++++++++----------- 1 file changed, 26 insertions(+), 38 deletions(-) diff --git a/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/Poll/PollOptionRow.swift b/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/Poll/PollOptionRow.swift index 852c628bf..f10c094f4 100644 --- a/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/Poll/PollOptionRow.swift +++ b/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/Poll/PollOptionRow.swift @@ -57,44 +57,32 @@ public struct PollOptionRow: View { } } } else { - // beautiful! - if let moveUp { - if let moveDown { - if let removeOption { - field - .accessibilityAction(named: L10n.Scene.Compose.Poll.moveUp, moveUp) - .accessibilityAction(named: L10n.Scene.Compose.Poll.moveDown, moveDown) - .accessibilityAction(named: L10n.Scene.Compose.Poll.removeOption, removeOption) - } else { - field - .accessibilityAction(named: L10n.Scene.Compose.Poll.moveUp, moveUp) - .accessibilityAction(named: L10n.Scene.Compose.Poll.moveDown, moveDown) - } - } else { - if let removeOption { - field - .accessibilityAction(named: L10n.Scene.Compose.Poll.moveUp, moveUp) - .accessibilityAction(named: L10n.Scene.Compose.Poll.removeOption, removeOption) - } else { - field.accessibilityAction(named: L10n.Scene.Compose.Poll.moveUp, moveUp) - } - } - } else { - if let moveDown { - if let removeOption { - field - .accessibilityAction(named: L10n.Scene.Compose.Poll.moveDown, moveDown) - .accessibilityAction(named: L10n.Scene.Compose.Poll.removeOption, removeOption) - } else { - field.accessibilityAction(named: L10n.Scene.Compose.Poll.moveDown, moveDown) - } - } else { - if let removeOption { - field.accessibilityAction(named: L10n.Scene.Compose.Poll.removeOption, removeOption) - } else { - field - } - } + switch (moveUp, moveDown, removeOption) { + case let (.some(up), .some(down), .some(remove)): + field + .accessibilityAction(named: L10n.Scene.Compose.Poll.moveUp, up) + .accessibilityAction(named: L10n.Scene.Compose.Poll.moveDown, down) + .accessibilityAction(named: L10n.Scene.Compose.Poll.removeOption, remove) + case let (.some(up), .some(down), .none): + field + .accessibilityAction(named: L10n.Scene.Compose.Poll.moveUp, up) + .accessibilityAction(named: L10n.Scene.Compose.Poll.moveDown, down) + case let (.some(up), .none, .some(remove)): + field + .accessibilityAction(named: L10n.Scene.Compose.Poll.moveUp, up) + .accessibilityAction(named: L10n.Scene.Compose.Poll.removeOption, remove) + case let (.some(up), .none, .none): + field.accessibilityAction(named: L10n.Scene.Compose.Poll.moveUp, up) + case let (.none, .some(down), .some(remove)): + field + .accessibilityAction(named: L10n.Scene.Compose.Poll.moveDown, down) + .accessibilityAction(named: L10n.Scene.Compose.Poll.removeOption, remove) + case let (.none, .some(down), .none): + field.accessibilityAction(named: L10n.Scene.Compose.Poll.moveDown, down) + case let (.none, .none, .some(remove)): + field.accessibilityAction(named: L10n.Scene.Compose.Poll.removeOption, remove) + case (.none, .none, .none): + field } } }