forked from shadowfacts/Tusker
196 lines
6.4 KiB
Swift
196 lines
6.4 KiB
Swift
//
|
|
// PollController.swift
|
|
// ComposeUI
|
|
//
|
|
// Created by Shadowfacts on 3/25/23.
|
|
//
|
|
|
|
import SwiftUI
|
|
import TuskerComponents
|
|
|
|
class PollController: ViewController {
|
|
|
|
unowned let parent: ComposeController
|
|
var draft: Draft { parent.draft }
|
|
let poll: Poll
|
|
|
|
@Published var duration: Duration
|
|
|
|
init(parent: ComposeController, poll: Poll) {
|
|
self.parent = parent
|
|
self.poll = poll
|
|
self.duration = .fromTimeInterval(poll.duration) ?? .oneDay
|
|
}
|
|
|
|
var view: some View {
|
|
PollView()
|
|
.environmentObject(poll)
|
|
}
|
|
|
|
private func removePoll() {
|
|
withAnimation {
|
|
draft.poll = nil
|
|
}
|
|
}
|
|
|
|
private func moveOptions(indices: IndexSet, newIndex: Int) {
|
|
// see AttachmentsListController.moveAttachments
|
|
var array = poll.pollOptions
|
|
array.move(fromOffsets: indices, toOffset: newIndex)
|
|
poll.options = NSMutableOrderedSet(array: array)
|
|
}
|
|
|
|
private func removeOption(_ option: PollOption) {
|
|
var array = poll.pollOptions
|
|
array.remove(at: poll.options.index(of: option))
|
|
poll.options = NSMutableOrderedSet(array: array)
|
|
}
|
|
|
|
private var canAddOption: Bool {
|
|
if let max = parent.mastodonController.instanceFeatures.maxPollOptionsCount {
|
|
return poll.options.count < max
|
|
} else {
|
|
return true
|
|
}
|
|
}
|
|
|
|
private func addOption() {
|
|
let option = PollOption(context: DraftsPersistentContainer.shared.viewContext)
|
|
option.poll = poll
|
|
poll.options.add(option)
|
|
}
|
|
|
|
struct PollView: View {
|
|
@EnvironmentObject private var controller: PollController
|
|
@EnvironmentObject private var poll: Poll
|
|
@Environment(\.colorScheme) private var colorScheme
|
|
|
|
var body: some View {
|
|
VStack {
|
|
HStack {
|
|
Text("Poll")
|
|
.font(.headline)
|
|
|
|
Spacer()
|
|
|
|
Button(action: controller.removePoll) {
|
|
Image(systemName: "xmark")
|
|
.imageScale(.small)
|
|
.padding(4)
|
|
}
|
|
.accessibilityLabel("Remove poll")
|
|
.buttonStyle(.plain)
|
|
.accentColor(buttonForegroundColor)
|
|
.background(Circle().foregroundColor(buttonBackgroundColor))
|
|
.hoverEffect()
|
|
}
|
|
|
|
List {
|
|
ForEach($poll.pollOptions) { $option in
|
|
PollOptionView(option: option, remove: { controller.removeOption(option) })
|
|
.frame(height: 36)
|
|
.listRowInsets(EdgeInsets(top: 4, leading: 0, bottom: 4, trailing: 0))
|
|
.listRowSeparator(.hidden)
|
|
.listRowBackground(Color.clear)
|
|
}
|
|
.onMove(perform: controller.moveOptions)
|
|
}
|
|
.listStyle(.plain)
|
|
.scrollDisabledIfAvailable(true)
|
|
.frame(height: 44 * CGFloat(poll.options.count))
|
|
|
|
Button(action: controller.addOption) {
|
|
Label {
|
|
Text("Add Option")
|
|
} icon: {
|
|
Image(systemName: "plus")
|
|
.foregroundColor(.accentColor)
|
|
}
|
|
}
|
|
.buttonStyle(.borderless)
|
|
.disabled(!controller.canAddOption)
|
|
|
|
HStack {
|
|
MenuPicker(selection: $poll.multiple, options: [
|
|
.init(value: true, title: "Allow multiple"),
|
|
.init(value: false, title: "Single choice"),
|
|
])
|
|
.frame(maxWidth: .infinity)
|
|
|
|
MenuPicker(selection: $controller.duration, options: Duration.allCases.map {
|
|
.init(value: $0, title: Duration.formatter.string(from: $0.timeInterval)!)
|
|
})
|
|
.frame(maxWidth: .infinity)
|
|
}
|
|
}
|
|
.padding(8)
|
|
.background(
|
|
RoundedRectangle(cornerRadius: 10, style: .continuous)
|
|
.foregroundColor(backgroundColor)
|
|
)
|
|
#if os(visionOS)
|
|
.onChange(of: controller.duration) {
|
|
poll.duration = controller.duration.timeInterval
|
|
}
|
|
#else
|
|
.onChange(of: controller.duration) { newValue in
|
|
poll.duration = newValue.timeInterval
|
|
}
|
|
#endif
|
|
}
|
|
|
|
private var backgroundColor: Color {
|
|
// in light mode, .secondarySystemBackground has a blue-ish hue, which we don't want
|
|
colorScheme == .dark ? controller.parent.config.fillColor : Color(white: 0.95)
|
|
}
|
|
|
|
private var buttonForegroundColor: Color {
|
|
Color(uiColor: .label)
|
|
}
|
|
|
|
private var buttonBackgroundColor: Color {
|
|
Color(white: colorScheme == .dark ? 0.1 : 0.8)
|
|
}
|
|
}
|
|
}
|
|
|
|
extension PollController {
|
|
enum Duration: Hashable, Equatable, CaseIterable {
|
|
case fiveMinutes, thirtyMinutes, oneHour, sixHours, oneDay, threeDays, sevenDays
|
|
|
|
static let formatter: DateComponentsFormatter = {
|
|
let f = DateComponentsFormatter()
|
|
f.maximumUnitCount = 1
|
|
f.unitsStyle = .full
|
|
f.allowedUnits = [.weekOfMonth, .day, .hour, .minute]
|
|
return f
|
|
}()
|
|
|
|
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
|
|
}
|
|
}
|
|
}
|
|
}
|