// // ComposePollView.swift // Tusker // // Created by Shadowfacts on 4/28/21. // Copyright © 2021 Shadowfacts. All rights reserved. // import SwiftUI import TuskerComponents 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: OldDraft @ObservedObject var poll: OldDraft.Poll @EnvironmentObject var mastodonController: MastodonController @Environment(\.colorScheme) var colorScheme: ColorScheme @State private var duration: Duration init(draft: OldDraft, poll: OldDraft.Poll) { self.draft = draft self.poll = poll self._duration = State(initialValue: .fromTimeInterval(poll.duration) ?? .oneDay) } private var canAddOption: Bool { if let pollConfig = mastodonController.instance?.pollsConfiguration { return poll.options.count < pollConfig.maxOptions } else { return true } } var body: some View { VStack { HStack { Text("Poll") .font(.headline) Spacer() Button(action: self.removePoll) { Image(systemName: "xmark") .imageScale(.small) .padding(4) } .accessibilityLabel("Remove poll") .buttonStyle(.plain) .accentColor(buttonForegroundColor) .background(Circle().foregroundColor(buttonBackgroundColor)) .hoverEffect() } List { ForEach(Array(poll.options.enumerated()), id: \.element.id) { (e) in ComposePollOption(poll: poll, option: e.element, optionIndex: e.offset) .frame(height: 36) .listRowInsets(EdgeInsets(top: 4, leading: 0, bottom: 4, trailing: 0)) .listRowSeparator(.hidden) .listRowBackground(Color.clear) } .onMove { indices, newIndex in poll.options.move(fromOffsets: indices, toOffset: newIndex) } } .listStyle(.plain) .frame(height: 44 * CGFloat(poll.options.count)) Button(action: self.addOption) { Label { Text("Add Option") } icon: { Image(systemName: "plus") .foregroundColor(.accentColor) } } .buttonStyle(.borderless) .disabled(!canAddOption) HStack { MenuPicker(selection: $poll.multiple, options: [ .init(value: true, title: "Allow multiple"), .init(value: false, title: "Single choice"), ]) .frame(maxWidth: .infinity) MenuPicker(selection: $duration, options: Duration.allCases.map { .init(value: $0, title: ComposePollView.formatter.string(from: $0.timeInterval)!) }) .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.appFill : 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() { poll.options.append(OldDraft.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: OldDraft.Poll @ObservedObject var option: OldDraft.Poll.Option let optionIndex: Int @EnvironmentObject private var mastodonController: MastodonController var body: some View { HStack(spacing: 4) { Checkbox(radiusFraction: poll.multiple ? 0.1 : 0.5, borderWidth: 2) .animation(.default, value: poll.multiple) textField Button(action: self.removeOption) { Image(systemName: "minus.circle.fill") } .buttonStyle(.plain) .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)", maxLength: mastodonController.instance?.pollsConfiguration?.maxCharactersPerOption) return field.backgroundColor(.appBackground) } private func removeOption() { 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.appBackground)) .frame(width: innerSize, height: innerSize) .cornerRadius(radiusFraction * innerSize) } } } } //struct ComposePollView_Previews: PreviewProvider { // static var previews: some View { // ComposePollView() // } //}