Polls UI Polish
This commit is contained in:
parent
aac8b1f636
commit
150e670b33
@ -143,11 +143,11 @@ public class CVPollView: ManualStackView {
|
||||
|
||||
let circleSize = CGSize(square: 2)
|
||||
|
||||
var optionRowInnerStackConfig: CVStackViewConfig {
|
||||
func buildOptionRowInnerStackConfig(voteLabelWidth: Double) -> CVStackViewConfig {
|
||||
CVStackViewConfig(axis: .horizontal,
|
||||
alignment: .leading,
|
||||
spacing: 8,
|
||||
layoutMargins: UIEdgeInsets(top: 2, leading: 0, bottom: 2, trailing: 30))
|
||||
layoutMargins: UIEdgeInsets(top: 2, leading: 0, bottom: 2, trailing: voteLabelWidth))
|
||||
}
|
||||
}
|
||||
|
||||
@ -164,6 +164,22 @@ public class CVPollView: ManualStackView {
|
||||
)
|
||||
}
|
||||
|
||||
private static func localizedNumber(from votes: Int) -> String {
|
||||
let formatter: NumberFormatter = {
|
||||
let f = NumberFormatter()
|
||||
f.numberStyle = .decimal
|
||||
return f
|
||||
}()
|
||||
|
||||
return formatter.string(from: NSNumber(value: votes))!
|
||||
}
|
||||
|
||||
private static func voteLabelWidthWithPadding(localizedVotes: String) -> Double {
|
||||
let attributes = [NSAttributedString.Key.font: UIFont.dynamicTypeBody]
|
||||
let textSize = localizedVotes.size(withAttributes: attributes)
|
||||
return textSize.width + 4
|
||||
}
|
||||
|
||||
static func measure(
|
||||
maxWidth: CGFloat,
|
||||
measurementBuilder: CVCellMeasurement.Builder,
|
||||
@ -226,9 +242,22 @@ public class CVPollView: ManualStackView {
|
||||
lineBreakMode: .byWordWrapping
|
||||
)
|
||||
|
||||
let maxOptionLabelWidth = (maxLabelWidth - (configurator.optionRowInnerStackConfig.layoutMargins.right +
|
||||
configurator.checkBoxSize.width +
|
||||
configurator.optionRowInnerStackConfig.spacing))
|
||||
let hasLocalUserVoted = option.localUserHasVoted(localAci: state.localAci)
|
||||
|
||||
// When the poll is ended, the checkbox should be removed except for options
|
||||
// the local user voted for. Those checkboxes should be shifted right.
|
||||
// In order to make sure they don't overlap with vote count, we need to measure
|
||||
// the vote count width and update the option row stack config trailing
|
||||
// spacing accordingly.
|
||||
let checkboxSize = poll.isEnded && !hasLocalUserVoted ? 0 : configurator.checkBoxSize.width
|
||||
|
||||
let localizedVotesString = localizedNumber(from: option.acis.count)
|
||||
let voteLabelWidth = voteLabelWidthWithPadding(localizedVotes: localizedVotesString)
|
||||
let innerStackConfig = configurator.buildOptionRowInnerStackConfig(voteLabelWidth: voteLabelWidth)
|
||||
|
||||
let maxOptionLabelWidth = (maxLabelWidth - (innerStackConfig.layoutMargins.right +
|
||||
checkboxSize +
|
||||
innerStackConfig.spacing))
|
||||
|
||||
let optionLabelTextSize = CVText.measureLabel(
|
||||
config: optionTextConfig,
|
||||
@ -241,11 +270,22 @@ public class CVPollView: ManualStackView {
|
||||
width: maxOptionLabelWidth,
|
||||
height: optionLabelTextSize.height
|
||||
)
|
||||
|
||||
var subViewInfos: [ManualStackSubviewInfo] = []
|
||||
if poll.isEnded {
|
||||
subViewInfos = [optionRowSize.asManualSubviewInfo]
|
||||
if hasLocalUserVoted {
|
||||
subViewInfos.append(configurator.checkBoxSize.asManualSubviewInfo(hasFixedSize: true))
|
||||
}
|
||||
} else {
|
||||
subViewInfos = [configurator.checkBoxSize.asManualSubviewInfo(hasFixedSize: true), optionRowSize.asManualSubviewInfo]
|
||||
}
|
||||
|
||||
let optionRowInnerMeasurement = ManualStackView.measure(
|
||||
config: configurator.optionRowInnerStackConfig,
|
||||
config: innerStackConfig,
|
||||
measurementBuilder: measurementBuilder,
|
||||
measurementKey: measurementKey_optionRowInnerStack + String(option.optionIndex),
|
||||
subviewInfos: [configurator.checkBoxSize.asManualSubviewInfo(hasFixedSize: true), optionRowSize.asManualSubviewInfo]
|
||||
subviewInfos: subViewInfos
|
||||
)
|
||||
|
||||
let progressBarSize = CGSize(width: maxLabelWidth, height: 8)
|
||||
@ -343,7 +383,8 @@ public class CVPollView: ManualStackView {
|
||||
voteType: voteType,
|
||||
delegate: componentDelegate
|
||||
)
|
||||
}
|
||||
},
|
||||
pollIsEnded: poll.isEnded
|
||||
)
|
||||
optionSubviews.append(row)
|
||||
}
|
||||
@ -417,6 +458,7 @@ public class CVPollView: ManualStackView {
|
||||
totalVotes: Int,
|
||||
hasLocalUserAlreadyVoted: Bool,
|
||||
pollVoteHandler: @escaping (VoteType) -> Void,
|
||||
pollIsEnded: Bool
|
||||
) {
|
||||
self.pollVoteHandler = pollVoteHandler
|
||||
|
||||
@ -429,7 +471,8 @@ public class CVPollView: ManualStackView {
|
||||
votes: pollOption.acis.count,
|
||||
totalVotes: totalVotes,
|
||||
hasLocalUserAlreadyVoted: hasLocalUserAlreadyVoted,
|
||||
isPending: pollOption.isPending
|
||||
isPending: pollOption.isPending,
|
||||
pollIsEnded: pollIsEnded
|
||||
)
|
||||
}
|
||||
|
||||
@ -497,7 +540,8 @@ public class CVPollView: ManualStackView {
|
||||
votes: Int,
|
||||
totalVotes: Int,
|
||||
hasLocalUserAlreadyVoted: Bool,
|
||||
isPending: Bool
|
||||
isPending: Bool,
|
||||
pollIsEnded: Bool
|
||||
) {
|
||||
checkbox.setImage(UIImage(named: Theme.iconName(.checkCircleFill)), for: .selected)
|
||||
checkbox.setImage(UIImage(named: Theme.iconName(.circle)), for: .normal)
|
||||
@ -517,17 +561,32 @@ public class CVPollView: ManualStackView {
|
||||
)
|
||||
optionTextConfig.applyForRendering(label: optionText)
|
||||
|
||||
var subviews: [UIView] = []
|
||||
if pollIsEnded {
|
||||
checkbox.isUserInteractionEnabled = false
|
||||
subviews = [optionText]
|
||||
if hasLocalUserAlreadyVoted {
|
||||
subviews.append(checkbox)
|
||||
}
|
||||
} else {
|
||||
subviews = [checkbox, optionText]
|
||||
}
|
||||
|
||||
let localizedVotesString = localizedNumber(from: votes)
|
||||
let voteLabelWidth = voteLabelWidthWithPadding(localizedVotes: localizedVotesString)
|
||||
let innerStackConfig = configurator.buildOptionRowInnerStackConfig(voteLabelWidth: voteLabelWidth)
|
||||
|
||||
innerStack.configure(
|
||||
config: configurator.optionRowInnerStackConfig,
|
||||
config: innerStackConfig,
|
||||
cellMeasurement: cellMeasurement,
|
||||
measurementKey: measurementKey_optionRowInnerStack + String(index),
|
||||
subviews: [checkbox, optionText]
|
||||
subviews: subviews
|
||||
)
|
||||
|
||||
innerStackContainer.addSubviewToFillSuperviewEdges(innerStack)
|
||||
|
||||
let numVotesConfig = CVLabelConfig.unstyledText(
|
||||
String(votes), // TODO: Localize number
|
||||
localizedVotesString,
|
||||
font: UIFont.dynamicTypeBody,
|
||||
textColor: configurator.textColor,
|
||||
numberOfLines: 0,
|
||||
|
||||
@ -40,7 +40,7 @@ public class CVComponentPoll: CVComponentBase, CVComponent {
|
||||
public func measure(maxWidth: CGFloat, measurementBuilder: SignalUI.CVCellMeasurement.Builder) -> CGSize {
|
||||
owsAssertDebug(maxWidth > 0)
|
||||
|
||||
let maxWidth = min(maxWidth, conversationStyle.maxMediaMessageWidth)
|
||||
let maxWidth = min(maxWidth, conversationStyle.maxMessageWidth)
|
||||
return CVPollView.measure(maxWidth: maxWidth, measurementBuilder: measurementBuilder, state: poll.state)
|
||||
}
|
||||
|
||||
|
||||
@ -57,6 +57,13 @@ struct PollDetailsView: View {
|
||||
fileprivate let viewModel: PollDetailsViewModel
|
||||
private var poll: OWSPoll
|
||||
|
||||
var titleString: String {
|
||||
if poll.isEnded {
|
||||
return OWSLocalizedString("POLL_RESULTS_TITLE", comment: "Title of poll details pane when poll is ended")
|
||||
}
|
||||
return OWSLocalizedString("POLL_DETAILS_TITLE", comment: "Title of poll details pane")
|
||||
}
|
||||
|
||||
fileprivate init(poll: OWSPoll, viewModel: PollDetailsViewModel) {
|
||||
self.poll = poll
|
||||
self.viewModel = viewModel
|
||||
@ -65,7 +72,7 @@ struct PollDetailsView: View {
|
||||
var body: some View {
|
||||
VStack(spacing: 0) {
|
||||
ZStack {
|
||||
Text(OWSLocalizedString("POLL_DETAILS_TITLE", comment: "Title of poll details pane"))
|
||||
Text(titleString)
|
||||
.font(.headline)
|
||||
.fontWeight(.semibold)
|
||||
.frame(maxWidth: .infinity, alignment: .center)
|
||||
@ -147,7 +154,7 @@ struct PollDetailsView: View {
|
||||
|
||||
private func addressCell(address: SignalServiceAddress) -> ManualStackView? {
|
||||
let cell = ContactCellView()
|
||||
let config = ContactCellConfiguration(address: address, localUserDisplayMode: .noteToSelf)
|
||||
let config = ContactCellConfiguration(address: address, localUserDisplayMode: .asLocalUser)
|
||||
config.avatarSizeClass = .twentyEight
|
||||
|
||||
SSKEnvironment.shared.databaseStorageRef.read { transaction in
|
||||
|
||||
@ -6521,7 +6521,7 @@
|
||||
"POLL_DETAILS_TITLE" = "Poll Details";
|
||||
|
||||
/* Shown when another user ends a poll. Embeds {{ another user }} and {{ poll question }}. */
|
||||
"POLL_ENDED_BY_OTHER_CHAT_LIST_UPDATE" = "“%@” ended the poll: “%@”";
|
||||
"POLL_ENDED_BY_OTHER_CHAT_LIST_UPDATE" = "%@ ended the poll: %@";
|
||||
|
||||
/* Notification that {{contact}} ended a poll with question {{poll question}} */
|
||||
"POLL_ENDED_NOTIFICATION" = "%@ ended the poll “%@”";
|
||||
@ -6550,6 +6550,9 @@
|
||||
/* Placeholder text for poll question */
|
||||
"POLL_QUESTION_PLACEHOLDER_TEXT" = "Ask a Question";
|
||||
|
||||
/* Title of poll details pane when poll is ended */
|
||||
"POLL_RESULTS_TITLE" = "Poll Results";
|
||||
|
||||
/* Label specifying the user can select more than one option */
|
||||
"POLL_SELECT_LABEL_MULTIPLE" = "Select one or more";
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user