Skip to content

Commit

Permalink
Polls refinements (#1608)
Browse files Browse the repository at this point in the history
* Refine focus behavior

* Begin draft mode

* Add alert on poll form

* Add poll ended asset

* Add fallback text for ended poll event

* Cleanup

* Fix assets

* Remove poll feature flags

* Fix UI tests

* Fix ui tests

* Refine discard poll alert

* Remove unused import

* Rename hasDraftContent -> hasContent

* Restore createPoll-2 ref screenshots
  • Loading branch information
alfogrillo authored Sep 1, 2023
1 parent 41662a5 commit 9b313b8
Show file tree
Hide file tree
Showing 36 changed files with 169 additions and 91 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"images" : [
{
"filename" : "ended-poll.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"template-rendering-intent" : "template"
}
}
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"images" : [
{
"filename" : "timeline-ended-poll.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true,
"template-rendering-intent" : "template"
}
}
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true,
"template-rendering-intent" : "template"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true,
"template-rendering-intent" : "template"
}
}
11 changes: 9 additions & 2 deletions ElementX/Resources/Localizations/en.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@
"common_success" = "Success";
"common_suggestions" = "Suggestions";
"common_syncing" = "Syncing";
"common_text" = "Text";
"common_third_party_notices" = "Third-party notices";
"common_topic" = "Topic";
"common_topic_placeholder" = "What is this room about?";
Expand Down Expand Up @@ -182,10 +183,12 @@
"rageshake_dialog_content" = "You seem to be shaking the phone in frustration. Would you like to open the bug report screen?";
"report_content_explanation" = "This message will be reported to your homeserver’s administrator. They will not be able to read any encrypted messages.";
"report_content_hint" = "Reason for reporting this content";
"rich_text_editor_a11y_add_attachment" = "Add attachment";
"rich_text_editor_bullet_list" = "Toggle bullet list";
"rich_text_editor_close_formatting_options" = "Close formatting options";
"rich_text_editor_code_block" = "Toggle code block";
"rich_text_editor_composer_placeholder" = "Message…";
"rich_text_editor_create_link" = "Create a link";
"rich_text_editor_edit_link" = "Edit link";
"rich_text_editor_format_bold" = "Apply bold format";
"rich_text_editor_format_italic" = "Apply italic format";
"rich_text_editor_format_strikethrough" = "Apply strikethrough format";
Expand All @@ -195,8 +198,11 @@
"rich_text_editor_inline_code" = "Apply inline code format";
"rich_text_editor_link" = "Set link";
"rich_text_editor_numbered_list" = "Toggle numbered list";
"rich_text_editor_open_compose_options" = "Open compose options";
"rich_text_editor_quote" = "Toggle quote";
"rich_text_editor_unindent" = "Unindent";
"rich_text_editor_url_placeholder" = "Link";
"rich_text_editor_a11y_add_attachment" = "Add attachment";
"room_timeline_beginning_of_room" = "This is the beginning of %1$@.";
"room_timeline_beginning_of_room_no_name" = "This is the beginning of this conversation.";
"room_timeline_read_marker_title" = "New";
Expand Down Expand Up @@ -241,7 +247,8 @@
"screen_create_poll_anonymous_desc" = "Show results only after poll ends";
"screen_create_poll_anonymous_headline" = "Anonymous Poll";
"screen_create_poll_answer_hint" = "Option %1$d";
"screen_create_poll_confirmation" = "Are you sure you would like to go back?";
"screen_create_poll_discard_confirmation" = "Are you sure you want to discard this poll?";
"screen_create_poll_discard_confirmation_title" = "Discard Poll";
"screen_create_poll_question_desc" = "Question or topic";
"screen_create_poll_question_hint" = "What is the poll about?";
"screen_create_poll_title" = "Create Poll";
Expand Down
8 changes: 0 additions & 8 deletions ElementX/Sources/Application/AppSettings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,7 @@ final class AppSettings {
case hasShownWelcomeScreen
case notificationSettingsEnabled
case swiftUITimelineEnabled
case pollsInTimeline
case richTextEditorEnabled
case pollsCreationEnabled
}

private static var suiteName: String = InfoPlistReader.main.appGroupIdentifier
Expand Down Expand Up @@ -242,12 +240,6 @@ final class AppSettings {
@UserPreference(key: UserDefaultsKeys.swiftUITimelineEnabled, defaultValue: false, storageType: .volatile)
var swiftUITimelineEnabled

@UserPreference(key: UserDefaultsKeys.pollsInTimeline, defaultValue: false, storageType: .userDefaults(store))
var pollsInTimelineEnabled

@UserPreference(key: UserDefaultsKeys.richTextEditorEnabled, defaultValue: false, storageType: .userDefaults(store))
var richTextEditorEnabled

@UserPreference(key: UserDefaultsKeys.pollsCreationEnabled, defaultValue: false, storageType: .userDefaults(store))
var pollsCreationEnabled
}
2 changes: 2 additions & 0 deletions ElementX/Sources/Generated/Assets.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,15 @@ internal enum Asset {
internal static let encryptionNormal = ImageAsset(name: "images/encryption-normal")
internal static let encryptionTrusted = ImageAsset(name: "images/encryption-trusted")
internal static let encryptionWarning = ImageAsset(name: "images/encryption-warning")
internal static let endedPoll = ImageAsset(name: "images/ended-poll")
internal static let launchBackground = ImageAsset(name: "images/launch-background")
internal static let launchLogo = ImageAsset(name: "images/launch-logo")
internal static let locationMarker = ImageAsset(name: "images/location-marker")
internal static let locationPin = ImageAsset(name: "images/location-pin")
internal static let locationPointerFull = ImageAsset(name: "images/location-pointer-full")
internal static let locationPointer = ImageAsset(name: "images/location-pointer")
internal static let timelineComposerSendMessage = ImageAsset(name: "images/timeline-composer-send-message")
internal static let timelineEndedPoll = ImageAsset(name: "images/timeline-ended-poll")
internal static let timelinePollAttachment = ImageAsset(name: "images/timeline-poll-attachment")
internal static let timelinePoll = ImageAsset(name: "images/timeline-poll")
internal static let timelineReactionAddMore = ImageAsset(name: "images/timeline-reaction-add-more")
Expand Down
18 changes: 16 additions & 2 deletions ElementX/Sources/Generated/Strings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,8 @@ public enum L10n {
public static var commonSuggestions: String { return L10n.tr("Localizable", "common_suggestions") }
/// Syncing
public static var commonSyncing: String { return L10n.tr("Localizable", "common_syncing") }
/// Text
public static var commonText: String { return L10n.tr("Localizable", "common_text") }
/// Third-party notices
public static var commonThirdPartyNotices: String { return L10n.tr("Localizable", "common_third_party_notices") }
/// Topic
Expand Down Expand Up @@ -462,10 +464,16 @@ public enum L10n {
public static var richTextEditorA11yAddAttachment: String { return L10n.tr("Localizable", "rich_text_editor_a11y_add_attachment") }
/// Toggle bullet list
public static var richTextEditorBulletList: String { return L10n.tr("Localizable", "rich_text_editor_bullet_list") }
/// Close formatting options
public static var richTextEditorCloseFormattingOptions: String { return L10n.tr("Localizable", "rich_text_editor_close_formatting_options") }
/// Toggle code block
public static var richTextEditorCodeBlock: String { return L10n.tr("Localizable", "rich_text_editor_code_block") }
/// Message…
public static var richTextEditorComposerPlaceholder: String { return L10n.tr("Localizable", "rich_text_editor_composer_placeholder") }
/// Create a link
public static var richTextEditorCreateLink: String { return L10n.tr("Localizable", "rich_text_editor_create_link") }
/// Edit link
public static var richTextEditorEditLink: String { return L10n.tr("Localizable", "rich_text_editor_edit_link") }
/// Apply bold format
public static var richTextEditorFormatBold: String { return L10n.tr("Localizable", "rich_text_editor_format_bold") }
/// Apply italic format
Expand All @@ -484,10 +492,14 @@ public enum L10n {
public static var richTextEditorLink: String { return L10n.tr("Localizable", "rich_text_editor_link") }
/// Toggle numbered list
public static var richTextEditorNumberedList: String { return L10n.tr("Localizable", "rich_text_editor_numbered_list") }
/// Open compose options
public static var richTextEditorOpenComposeOptions: String { return L10n.tr("Localizable", "rich_text_editor_open_compose_options") }
/// Toggle quote
public static var richTextEditorQuote: String { return L10n.tr("Localizable", "rich_text_editor_quote") }
/// Unindent
public static var richTextEditorUnindent: String { return L10n.tr("Localizable", "rich_text_editor_unindent") }
/// Link
public static var richTextEditorUrlPlaceholder: String { return L10n.tr("Localizable", "rich_text_editor_url_placeholder") }
/// This is the beginning of %1$@.
public static func roomTimelineBeginningOfRoom(_ p1: Any) -> String {
return L10n.tr("Localizable", "room_timeline_beginning_of_room", String(describing: p1))
Expand Down Expand Up @@ -612,8 +624,10 @@ public enum L10n {
public static func screenCreatePollAnswerHint(_ p1: Int) -> String {
return L10n.tr("Localizable", "screen_create_poll_answer_hint", p1)
}
/// Are you sure you would like to go back?
public static var screenCreatePollConfirmation: String { return L10n.tr("Localizable", "screen_create_poll_confirmation") }
/// Are you sure you want to discard this poll?
public static var screenCreatePollDiscardConfirmation: String { return L10n.tr("Localizable", "screen_create_poll_discard_confirmation") }
/// Discard Poll
public static var screenCreatePollDiscardConfirmationTitle: String { return L10n.tr("Localizable", "screen_create_poll_discard_confirmation_title") }
/// Question or topic
public static var screenCreatePollQuestionDesc: String { return L10n.tr("Localizable", "screen_create_poll_question_desc") }
/// What is the poll about?
Expand Down
32 changes: 16 additions & 16 deletions ElementX/Sources/Mocks/Generated/GeneratedMocks.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1508,23 +1508,23 @@ class RoomProxyMock: RoomProxyProtocol {
}
//MARK: - endPoll

var endPollPollStartIDCallsCount = 0
var endPollPollStartIDCalled: Bool {
return endPollPollStartIDCallsCount > 0
}
var endPollPollStartIDReceivedPollStartID: String?
var endPollPollStartIDReceivedInvocations: [String] = []
var endPollPollStartIDReturnValue: Result<Void, RoomProxyError>!
var endPollPollStartIDClosure: ((String) async -> Result<Void, RoomProxyError>)?

func endPoll(pollStartID: String) async -> Result<Void, RoomProxyError> {
endPollPollStartIDCallsCount += 1
endPollPollStartIDReceivedPollStartID = pollStartID
endPollPollStartIDReceivedInvocations.append(pollStartID)
if let endPollPollStartIDClosure = endPollPollStartIDClosure {
return await endPollPollStartIDClosure(pollStartID)
var endPollPollStartIDTextCallsCount = 0
var endPollPollStartIDTextCalled: Bool {
return endPollPollStartIDTextCallsCount > 0
}
var endPollPollStartIDTextReceivedArguments: (pollStartID: String, text: String)?
var endPollPollStartIDTextReceivedInvocations: [(pollStartID: String, text: String)] = []
var endPollPollStartIDTextReturnValue: Result<Void, RoomProxyError>!
var endPollPollStartIDTextClosure: ((String, String) async -> Result<Void, RoomProxyError>)?

func endPoll(pollStartID: String, text: String) async -> Result<Void, RoomProxyError> {
endPollPollStartIDTextCallsCount += 1
endPollPollStartIDTextReceivedArguments = (pollStartID: pollStartID, text: text)
endPollPollStartIDTextReceivedInvocations.append((pollStartID: pollStartID, text: text))
if let endPollPollStartIDTextClosure = endPollPollStartIDTextClosure {
return await endPollPollStartIDTextClosure(pollStartID, text)
} else {
return endPollPollStartIDReturnValue
return endPollPollStartIDTextReturnValue
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,13 +66,11 @@ struct RoomAttachmentPicker: View {
}
.accessibilityIdentifier(A11yIdentifiers.roomScreen.attachmentPickerLocation)

if ServiceLocator.shared.settings.pollsCreationEnabled {
Button {
context.showAttachmentPopover = false
context.send(viewAction: .displayPollForm)
} label: {
PickerLabel(title: L10n.screenRoomAttachmentSourcePoll, icon: Image(asset: Asset.Images.timelinePollAttachment))
}
Button {
context.showAttachmentPopover = false
context.send(viewAction: .displayPollForm)
} label: {
PickerLabel(title: L10n.screenRoomAttachmentSourcePoll, icon: Image(asset: Asset.Images.timelinePollAttachment))
}
}
.padding(.top, isPresented ? 20 : 0)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,12 @@ struct CreatePollScreenViewStateBindings {
var isCreateButtonDisabled: Bool {
question.isEmpty || options.count < 2 || options.contains { $0.text.isEmpty }
}

var hasContent: Bool {
!question.isEmpty || options.contains(where: { !$0.text.isEmpty }) || isUndisclosed
}

var alertInfo: AlertInfo<UUID>?
}

enum CreatePollScreenViewAction {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,15 @@ class CreatePollScreenViewModel: CreatePollScreenViewModelType, CreatePollScreen
options: state.bindings.options.map(\.text),
pollKind: state.bindings.isUndisclosed ? .undisclosed : .disclosed))
case .cancel:
actionsSubject.send(.cancel)
if state.bindings.hasContent {
state.bindings.alertInfo = .init(id: .init(),
title: L10n.screenCreatePollDiscardConfirmationTitle,
message: L10n.screenCreatePollDiscardConfirmation,
primaryButton: .init(title: L10n.actionCancel, action: nil),
secondaryButton: .init(title: L10n.actionOk, action: { self.actionsSubject.send(.cancel) }))
} else {
actionsSubject.send(.cancel)
}
case .deleteOption(let index):
// fixes a crash that caused an index out of range when an option with the keyboard focus was deleted
Task {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ struct CreatePollScreen: View {
.navigationBarTitleDisplayMode(.inline)
.toolbar { toolbar }
.animation(.elementDefault, value: context.options)
.interactiveDismissDisabled(context.viewState.bindings.hasContent)
.alert(item: $context.alertInfo)
}

// MARK: - Private
Expand All @@ -55,6 +57,10 @@ struct CreatePollScreen: View {
.textFieldStyle(.compoundForm)
.focused($focus, equals: .question)
.accessibilityIdentifier(A11yIdentifiers.createPollScreen.question)
.onSubmit {
focus = context.options.indices.first.map { .option(index: $0) }
}
.submitLabel(.next)
}
.compoundFormSection()
}
Expand All @@ -74,6 +80,11 @@ struct CreatePollScreen: View {
}
.focused($focus, equals: .option(index: index))
.accessibilityIdentifier(A11yIdentifiers.createPollScreen.optionID(index))
.onSubmit {
let nextOptionIndex = index == context.options.endIndex - 1 ? nil : index + 1
focus = nextOptionIndex.map { .option(index: $0) }
}
.submitLabel(index == context.options.endIndex - 1 ? .done : .next)
}
}
.onMove { offsets, toOffset in
Expand All @@ -83,6 +94,7 @@ struct CreatePollScreen: View {
if context.options.count < context.viewState.maxNumberOfOptions {
Button(L10n.screenCreatePollAddOptionBtn) {
context.send(viewAction: .addOption)
focus = context.options.indices.last.map { .option(index: $0) }
}
.accessibilityIdentifier(A11yIdentifiers.createPollScreen.addOption)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -814,7 +814,8 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol

private func endPoll(pollStartID: String) {
Task {
let endPollResult = await roomProxy.endPoll(pollStartID: pollStartID)
let endPollResult = await roomProxy.endPoll(pollStartID: pollStartID,
text: "The poll with event id: \(pollStartID) has ended")
switch endPollResult {
case .success:
break
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ struct PollRoomTimelineView: View {
@Environment(\.timelineStyle) var timelineStyle
@EnvironmentObject private var context: RoomScreenViewModel.Context
@ScaledMetric private var summaryPadding = 32
@ScaledMetric private var iconSize = 22

var body: some View {
TimelineStyler(timelineItem: timelineItem) {
Expand Down Expand Up @@ -58,7 +59,11 @@ struct PollRoomTimelineView: View {

private var questionView: some View {
HStack(alignment: .top, spacing: 12) {
Image(Asset.Images.timelinePoll.name)
let asset = poll.hasEnded ? Asset.Images.timelineEndedPoll : Asset.Images.timelinePoll

Image(asset.name)
.resizable()
.frame(width: iconSize, height: iconSize)

Text(poll.question)
.multilineTextAlignment(.leading)
Expand Down
34 changes: 23 additions & 11 deletions ElementX/Sources/Screens/RoomScreen/View/TimelineItemMenu.swift
Original file line number Diff line number Diff line change
Expand Up @@ -97,19 +97,31 @@ enum TimelineItemMenuAction: Identifiable, Hashable {
}

/// The action's label.
@ViewBuilder
var label: some View {
switch self {
case .copy: return Label(L10n.actionCopy, systemImage: "doc.on.doc")
case .edit: return Label(L10n.actionEdit, systemImage: "pencil.line")
case .copyPermalink: return Label(L10n.actionCopyLinkToMessage, systemImage: "link")
case .reply: return Label(L10n.actionReply, systemImage: "arrowshape.turn.up.left")
case .forward: return Label(L10n.actionForward, systemImage: "arrowshape.turn.up.right")
case .redact: return Label(L10n.actionRemove, systemImage: "trash")
case .viewSource: return Label(L10n.actionViewSource, systemImage: "doc.text.below.ecg")
case .retryDecryption: return Label(L10n.actionRetryDecryption, systemImage: "arrow.down.message")
case .report: return Label(L10n.actionReportContent, systemImage: "exclamationmark.bubble")
case .react: return Label(L10n.actionReact, systemImage: "hand.thumbsup")
case .endPoll: return Label { Text(L10n.actionEndPoll) } icon: { Image.compound.check.resizable() }
case .copy:
Label(L10n.actionCopy, systemImage: "doc.on.doc")
case .edit:
Label(L10n.actionEdit, systemImage: "pencil.line")
case .copyPermalink:
Label(L10n.actionCopyLinkToMessage, systemImage: "link")
case .reply:
Label(L10n.actionReply, systemImage: "arrowshape.turn.up.left")
case .forward:
Label(L10n.actionForward, systemImage: "arrowshape.turn.up.right")
case .redact:
Label(L10n.actionRemove, systemImage: "trash")
case .viewSource:
Label(L10n.actionViewSource, systemImage: "doc.text.below.ecg")
case .retryDecryption:
Label(L10n.actionRetryDecryption, systemImage: "arrow.down.message")
case .report:
Label(L10n.actionReportContent, systemImage: "exclamationmark.bubble")
case .react:
Label(L10n.actionReact, systemImage: "hand.thumbsup")
case .endPoll:
Label { Text(L10n.actionEndPoll) } icon: { CompoundIcon(customImage: Asset.Images.endedPoll.swiftUIImage) }
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,7 @@ protocol DeveloperOptionsProtocol: AnyObject {
var readReceiptsEnabled: Bool { get set }
var notificationSettingsEnabled: Bool { get set }
var swiftUITimelineEnabled: Bool { get set }
var pollsInTimelineEnabled: Bool { get set }
var richTextEditorEnabled: Bool { get set }
var pollsCreationEnabled: Bool { get set }
}

extension AppSettings: DeveloperOptionsProtocol { }
Loading

0 comments on commit 9b313b8

Please sign in to comment.