Improve StatusPollView (#1929)

* fix `StatusPollView`

* fix text alignment
This commit is contained in:
Thai D. V 2024-01-30 15:22:20 +07:00 committed by GitHub
parent b8cf446406
commit 7268b5a38e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -20,26 +20,17 @@ public struct StatusPollView: View {
self.status = status self.status = status
} }
private func widthForOption(option: Poll.Option, proxy: GeometryProxy) -> CGFloat { private func relativePercent(for vote: Int) -> CGFloat {
if viewModel.poll.safeVotersCount != 0 { let biggestVote = viewModel.poll.options.compactMap { $0.votesCount }.max() ?? 0
let totalWidth = proxy.frame(in: .local).width guard biggestVote > 0 else { return 0 }
return totalWidth * ratioForOption(option: option)
} else { return if vote == biggestVote { 100 } else { CGFloat(vote) * 100 / CGFloat(biggestVote) }
return 0
}
} }
private func percentForOption(option: Poll.Option) -> Int { private func absolutePercent(for vote: Int) -> Int {
let percent = ratioForOption(option: option) * 100 let totalVote = viewModel.poll.safeVotersCount
return Int(round(percent)) guard totalVote > 0 else { return 0 }
} return Int(round(CGFloat(vote) * 100 / CGFloat(totalVote)))
private func ratioForOption(option: Poll.Option) -> CGFloat {
if let votesCount = option.votesCount, viewModel.poll.safeVotersCount != 0 {
CGFloat(votesCount) / CGFloat(viewModel.poll.safeVotersCount)
} else {
0.0
}
} }
private func isSelected(option: Poll.Option) -> Bool { private func isSelected(option: Poll.Option) -> Bool {
@ -84,14 +75,10 @@ public struct StatusPollView: View {
.disabled(isInteractive == false) .disabled(isInteractive == false)
} }
if viewModel.showResults || status.account.id == currentAccount.account?.id { if viewModel.showResults || status.account.id == currentAccount.account?.id {
Spacer()
// Make sure they're all the same width using a ZStack with 100% hiding behind the // Make sure they're all the same width using a ZStack with 100% hiding behind the
// real percentage. // real percentage.
ZStack(alignment: .trailing) { Text("100%").hidden().overlay(alignment: .trailing) {
Text("100%") Text("\(absolutePercent(for:option.votesCount ?? 0))%")
.hidden()
Text("\(percentForOption(option: option))%")
.font(.scaledSubheadline) .font(.scaledSubheadline)
} }
} }
@ -106,14 +93,8 @@ public struct StatusPollView: View {
if !viewModel.poll.expired, !(viewModel.poll.voted ?? false) { if !viewModel.poll.expired, !(viewModel.poll.voted ?? false) {
HStack { HStack {
if !viewModel.votes.isEmpty { if !viewModel.votes.isEmpty {
Button("status.poll.send") { Button("status.poll.send") { Task { await viewModel.postVotes() } }
Task { .buttonStyle(.borderedProminent)
do {
await viewModel.postVotes()
}
}
}
.buttonStyle(.borderedProminent)
} }
Button(viewModel.showResults ? "status.poll.hide-results" : "status.poll.show-results") { Button(viewModel.showResults ? "status.poll.hide-results" : "status.poll.show-results") {
withAnimation { withAnimation {
@ -123,14 +104,13 @@ public struct StatusPollView: View {
.buttonStyle(.bordered) .buttonStyle(.bordered)
} }
} }
footerView footerView
}.onAppear { }.onAppear {
viewModel.instance = currentInstance.instance viewModel.instance = currentInstance.instance
viewModel.client = client viewModel.client = client
Task { Task { await viewModel.fetchPoll() }
await viewModel.fetchPoll()
}
} }
.accessibilityElement(children: .contain) .accessibilityElement(children: .contain)
.accessibilityLabel(viewModel.poll.expired ? "accessibility.status.poll.finished.label" : "accessibility.status.poll.active.label") .accessibilityLabel(viewModel.poll.expired ? "accessibility.status.poll.finished.label" : "accessibility.status.poll.active.label")
@ -139,9 +119,9 @@ public struct StatusPollView: View {
func combinedAccessibilityLabel(for option: Poll.Option, index: Int) -> Text { func combinedAccessibilityLabel(for option: Poll.Option, index: Int) -> Text {
let showPercentage = viewModel.poll.expired || viewModel.poll.voted ?? false let showPercentage = viewModel.poll.expired || viewModel.poll.voted ?? false
return Text("accessibility.status.poll.option-prefix-\(index + 1)-of-\(viewModel.poll.options.count)") + return Text("accessibility.status.poll.option-prefix-\(index + 1)-of-\(viewModel.poll.options.count)") +
Text(", ") + Text(", ") +
Text(option.title) + Text(option.title) +
Text(showPercentage ? ", \(percentForOption(option: option))%" : "") Text(showPercentage ? ", \(absolutePercent(for:option.votesCount ?? 0))%" : "")
} }
private var footerView: some View { private var footerView: some View {
@ -176,40 +156,51 @@ public struct StatusPollView: View {
} }
} }
} label: { } label: {
GeometryReader { proxy in buttonImage
ZStack(alignment: .leading) { Spacer()
Rectangle()
.background { HStack {
if viewModel.showResults || status.account.id == currentAccount.account?.id { Text(option.title)
HStack { .multilineTextAlignment(.leading)
let width = widthForOption(option: option, proxy: proxy) .foregroundColor(theme.labelColor)
Rectangle() .font(.scaledBody)
.foregroundColor(theme.tintColor) .lineLimit(3)
.frame(width: width) .minimumScaleFactor(0.7)
if width != proxy.size.width { Spacer()
Spacer() }
} .padding(EdgeInsets(top: 5, leading: 10, bottom: 5, trailing: 10))
} .background(alignment: .leading) {
.transition(.asymmetric(insertion: .push(from: .leading), if viewModel.showResults || status.account.id == currentAccount.account?.id {
removal: .push(from: .trailing))) _PercentWidthLayout(percent: relativePercent(for: option.votesCount ?? 0)) {
} RoundedRectangle(cornerRadius: 10).foregroundColor(theme.tintColor)
} .transition(.asymmetric(insertion: .push(from: .leading),
.foregroundColor(theme.tintColor.opacity(0.40)) removal: .push(from: .trailing)))
.clipShape(RoundedRectangle(cornerRadius: 8))
HStack {
buttonImage
Text(option.title)
.foregroundColor(theme.labelColor)
.font(.scaledBody)
.lineLimit(3)
.minimumScaleFactor(0.7)
} }
.padding(.leading, 12)
} }
} }
.background { RoundedRectangle(cornerRadius: 10).fill(theme.tintColor.opacity(0.4)) }
.clipShape(RoundedRectangle(cornerRadius: 10))
} }
.buttonStyle(.borderless) .buttonStyle(.borderless)
.frame(minHeight: .pollBarHeight) .frame(minHeight: .pollBarHeight)
} }
private struct _PercentWidthLayout: Layout {
let percent: CGFloat
func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) -> CGSize {
guard let view = subviews.first else { return CGSize.zero }
return view.sizeThatFits(proposal)
}
func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) {
guard let view = subviews.first,
let width = proposal.width
else { return }
view.place(
at: bounds.origin,
proposal: ProposedViewSize(width: percent / 100 * width, height: proposal.height))
}
}
} }