213 lines
6.5 KiB
Swift
213 lines
6.5 KiB
Swift
//
|
|
// ComposePollView.swift
|
|
// Tusker
|
|
//
|
|
// Created by Shadowfacts on 4/28/21.
|
|
// Copyright © 2021 Shadowfacts. All rights reserved.
|
|
//
|
|
|
|
import SwiftUI
|
|
|
|
struct ComposePollView: View {
|
|
private static let formatter: DateComponentsFormatter = {
|
|
let f = DateComponentsFormatter()
|
|
f.maximumUnitCount = 1
|
|
f.unitsStyle = .full
|
|
f.allowedUnits = [.weekOfMonth, .day, .hour, .minute]
|
|
return f
|
|
}()
|
|
|
|
@ObservedObject var draft: Draft
|
|
@ObservedObject var poll: Draft.Poll
|
|
|
|
@Environment(\.colorScheme) var colorScheme: ColorScheme
|
|
|
|
@State private var duration: Duration
|
|
|
|
init(draft: Draft, poll: Draft.Poll) {
|
|
self.draft = draft
|
|
self.poll = poll
|
|
|
|
self._duration = State(initialValue: .fromTimeInterval(poll.duration) ?? .oneDay)
|
|
}
|
|
|
|
var body: some View {
|
|
VStack {
|
|
HStack {
|
|
Text("Poll")
|
|
.font(.headline)
|
|
|
|
Spacer()
|
|
|
|
Button(action: self.removePoll) {
|
|
Image(systemName: "xmark")
|
|
.imageScale(.small)
|
|
.padding(4)
|
|
}
|
|
.accentColor(buttonForegroundColor)
|
|
.background(Circle().foregroundColor(buttonBackgroundColor))
|
|
.hoverEffect()
|
|
}
|
|
|
|
ForEach(Array(poll.options.enumerated()), id: \.element.id) { (e) in
|
|
ComposePollOption(poll: poll, option: e.element, optionIndex: e.offset)
|
|
}
|
|
.transition(.slide)
|
|
|
|
Button(action: self.addOption) {
|
|
Label("Add Option", systemImage: "plus")
|
|
}
|
|
|
|
HStack {
|
|
// use .animation(nil) on pickers so frame doesn't have a size change animation when the text changes
|
|
Picker(selection: $poll.multiple, label: Text(poll.multiple ? "Allow multiple choices" : "Single choice")) {
|
|
Text("Allow multiple choices").tag(true)
|
|
Text("Single choice").tag(false)
|
|
}
|
|
.animation(nil)
|
|
.pickerStyle(MenuPickerStyle())
|
|
.frame(maxWidth: .infinity)
|
|
|
|
Picker(selection: $duration, label: Text(verbatim: ComposePollView.formatter.string(from: duration.timeInterval)!)) {
|
|
ForEach(Duration.allCases, id: \.self) { (duration) in
|
|
Text(ComposePollView.formatter.string(from: duration.timeInterval)!).tag(duration)
|
|
}
|
|
}
|
|
.animation(nil)
|
|
.pickerStyle(MenuPickerStyle())
|
|
.frame(maxWidth: .infinity)
|
|
}
|
|
}
|
|
.padding(8)
|
|
.background(
|
|
backgroundColor
|
|
.cornerRadius(10)
|
|
)
|
|
.onChange(of: duration, perform: { (value) in
|
|
poll.duration = value.timeInterval
|
|
})
|
|
}
|
|
|
|
private var backgroundColor: Color {
|
|
// in light mode, .secondarySystemBackground has a blue-ish hue, which we don't want
|
|
colorScheme == .dark ? Color(UIColor.secondarySystemBackground) : Color(white: 0.95)
|
|
}
|
|
|
|
private var buttonBackgroundColor: Color {
|
|
Color(white: colorScheme == .dark ? 0.1 : 0.8)
|
|
}
|
|
|
|
private var buttonForegroundColor: Color {
|
|
Color(UIColor.label)
|
|
}
|
|
|
|
private func removePoll() {
|
|
withAnimation {
|
|
self.draft.poll = nil
|
|
}
|
|
}
|
|
|
|
private func addOption() {
|
|
withAnimation(.easeInOut(duration: 0.25)) {
|
|
poll.options.append(Draft.Poll.Option(""))
|
|
}
|
|
}
|
|
}
|
|
|
|
extension ComposePollView {
|
|
enum Duration: Hashable, Equatable, CaseIterable {
|
|
case fiveMinutes, thirtyMinutes, oneHour, sixHours, oneDay, threeDays, sevenDays
|
|
|
|
static func fromTimeInterval(_ ti: TimeInterval) -> Duration? {
|
|
for it in allCases where it.timeInterval == ti {
|
|
return it
|
|
}
|
|
return nil
|
|
}
|
|
|
|
var timeInterval: TimeInterval {
|
|
switch self {
|
|
case .fiveMinutes:
|
|
return 5 * 60
|
|
case .thirtyMinutes:
|
|
return 30 * 60
|
|
case .oneHour:
|
|
return 60 * 60
|
|
case .sixHours:
|
|
return 6 * 60 * 60
|
|
case .oneDay:
|
|
return 24 * 60 * 60
|
|
case .threeDays:
|
|
return 3 * 24 * 60 * 60
|
|
case .sevenDays:
|
|
return 7 * 24 * 60 * 60
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
struct ComposePollOption: View {
|
|
@ObservedObject var poll: Draft.Poll
|
|
@ObservedObject var option: Draft.Poll.Option
|
|
let optionIndex: Int
|
|
|
|
var body: some View {
|
|
HStack(spacing: 4) {
|
|
Checkbox(radiusFraction: poll.multiple ? 0.1 : 0.5, borderWidth: 2)
|
|
.animation(.default)
|
|
|
|
|
|
textField
|
|
|
|
Button(action: self.removeOption) {
|
|
Image(systemName: "minus.circle.fill")
|
|
}
|
|
.foregroundColor(poll.options.count == 1 ? .gray : .red)
|
|
.disabled(poll.options.count == 1)
|
|
.hoverEffect()
|
|
}
|
|
}
|
|
|
|
private var textField: some View {
|
|
var field = ComposeEmojiTextField(text: $option.text, placeholder: "Option \(optionIndex + 1)")
|
|
return field.backgroundColor(.systemBackground)
|
|
}
|
|
|
|
private func removeOption() {
|
|
_ = withAnimation {
|
|
poll.options.remove(at: optionIndex)
|
|
}
|
|
}
|
|
|
|
struct Checkbox: View {
|
|
private let radiusFraction: CGFloat
|
|
private let size: CGFloat = 20
|
|
private let innerSize: CGFloat
|
|
|
|
init(radiusFraction: CGFloat, borderWidth: CGFloat) {
|
|
self.radiusFraction = radiusFraction
|
|
self.innerSize = self.size - 2 * borderWidth
|
|
}
|
|
|
|
var body: some View {
|
|
ZStack {
|
|
Rectangle()
|
|
.foregroundColor(.gray)
|
|
.frame(width: size, height: size)
|
|
.cornerRadius(radiusFraction * size)
|
|
|
|
Rectangle()
|
|
.foregroundColor(Color(UIColor.systemBackground))
|
|
.frame(width: innerSize, height: innerSize)
|
|
.cornerRadius(radiusFraction * innerSize)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//struct ComposePollView_Previews: PreviewProvider {
|
|
// static var previews: some View {
|
|
// ComposePollView()
|
|
// }
|
|
//}
|