UI improvements

main
Laurent 3 years ago
parent d0b31d2c3e
commit 04cc931ce8
  1. 8
      LeCountdown.xcodeproj/project.pbxproj
  2. 142
      LeCountdown/Views/ContentView.swift
  3. 12
      LeCountdown/Views/DialView.swift
  4. 40
      LeCountdown/Views/Reusable/SiriTimerView.swift
  5. 107
      LeCountdown/Views/TimersView.swift

@ -168,6 +168,8 @@
C4E5D68029B8FD93008E7465 /* Store.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E5D67F29B8FD93008E7465 /* Store.swift */; };
C4E5D68229B93583008E7465 /* PVP_Stab_Oneshot_Bleep_Em.wav in Resources */ = {isa = PBXBuildFile; fileRef = C4E5D68129B93583008E7465 /* PVP_Stab_Oneshot_Bleep_Em.wav */; };
C4E5D68429BB2425008E7465 /* SeparatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E5D68329BB2425008E7465 /* SeparatorView.swift */; };
C4E5D68629BB369E008E7465 /* TimersView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E5D68529BB369E008E7465 /* TimersView.swift */; };
C4E5D68829BB3FE1008E7465 /* SiriTimerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E5D68729BB3FE1008E7465 /* SiriTimerView.swift */; };
C4F8B1532987FE6F005C86A5 /* LaunchWidgetLiveActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = C438C7D72981216200BF3EF9 /* LaunchWidgetLiveActivity.swift */; };
C4F8B15729891271005C86A5 /* Conductor.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F8B15629891271005C86A5 /* Conductor.swift */; };
C4F8B15929891528005C86A5 /* forest_stream.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = C4F8B15829891528005C86A5 /* forest_stream.mp3 */; };
@ -385,6 +387,8 @@
C4E5D67F29B8FD93008E7465 /* Store.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Store.swift; sourceTree = "<group>"; };
C4E5D68129B93583008E7465 /* PVP_Stab_Oneshot_Bleep_Em.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = PVP_Stab_Oneshot_Bleep_Em.wav; sourceTree = "<group>"; };
C4E5D68329BB2425008E7465 /* SeparatorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeparatorView.swift; sourceTree = "<group>"; };
C4E5D68529BB369E008E7465 /* TimersView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimersView.swift; sourceTree = "<group>"; };
C4E5D68729BB3FE1008E7465 /* SiriTimerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SiriTimerView.swift; sourceTree = "<group>"; };
C4F8B15629891271005C86A5 /* Conductor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Conductor.swift; sourceTree = "<group>"; };
C4F8B15829891528005C86A5 /* forest_stream.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = forest_stream.mp3; sourceTree = "<group>"; };
C4F8B15E298961A7005C86A5 /* ReorderableForEach.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReorderableForEach.swift; sourceTree = "<group>"; };
@ -630,6 +634,7 @@
C498E59E298D4DEA00E90DE0 /* LiveTimerListView.swift */,
C4BA2B03299A42EF00CB4FBA /* NewDataView.swift */,
C4BA2B05299A8F8D00CB4FBA /* PresetsView.swift */,
C4E5D68529BB369E008E7465 /* TimersView.swift */,
C498E5A2298D720600E90DE0 /* TestView.swift */,
);
path = Views;
@ -777,6 +782,7 @@
C4F8B1D1298BF646005C86A5 /* PermissionAlertView.swift */,
C4F8B15E298961A7005C86A5 /* ReorderableForEach.swift */,
C4E5D68329BB2425008E7465 /* SeparatorView.swift */,
C4E5D68729BB3FE1008E7465 /* SiriTimerView.swift */,
C4F8B165298A9ABB005C86A5 /* SoundImageFormView.swift */,
C4BA2AD52993F62700CB4FBA /* SoundSelectionView.swift */,
C4BA2ADA299549BC00CB4FBA /* TimerModel.swift */,
@ -1029,6 +1035,7 @@
C4E5D68429BB2425008E7465 /* SeparatorView.swift in Sources */,
C4E5D67429B88734008E7465 /* DelaySoundPlayer.swift in Sources */,
C438C7FF2981300500BF3EF9 /* IntentDataProvider.swift in Sources */,
C4E5D68829BB3FE1008E7465 /* SiriTimerView.swift in Sources */,
C4E5D66629B73AED008E7465 /* StartTimerIntent.swift in Sources */,
C4BA2AD62993F62700CB4FBA /* SoundSelectionView.swift in Sources */,
C498E5A5299152B400E90DE0 /* GreenCheckmarkView.swift in Sources */,
@ -1073,6 +1080,7 @@
C40FDB622992985C0042A390 /* TextToSpeechRecorder.swift in Sources */,
C4BA2B43299FCB2B00CB4FBA /* RecordsView.swift in Sources */,
C4BA2B2F299E69A000CB4FBA /* View+Extension.swift in Sources */,
C4E5D68629BB369E008E7465 /* TimersView.swift in Sources */,
C4060DF7297AFEF2003FAB80 /* NewCountdownView.swift in Sources */,
C4060DCC297AE73D003FAB80 /* LeCountdown.xcdatamodeld in Sources */,
C4BA2B49299FCE0C00CB4FBA /* IntervalGroup+CoreDataProperties.swift in Sources */,

@ -8,7 +8,6 @@
import SwiftUI
import CoreData
import Combine
import _AppIntents_SwiftUI
class BoringContext : ObservableObject {
@ -16,7 +15,7 @@ class BoringContext : ObservableObject {
@Published var error: Error?
@Published var showDefaultAlert: Bool = false
@Published var showPermissionAlert: Bool = false
@Published var siriTimer: AbstractTimer? = nil
}
class TimerSpot : Identifiable, Equatable {
@ -49,87 +48,49 @@ struct ContentView<T : AbstractTimer>: View {
@Environment(\.managedObjectContext) private var viewContext
@FetchRequest(
sortDescriptors: [NSSortDescriptor(keyPath: \T.order, ascending: true)],
animation: .default)
private var timers: FetchedResults<T>
@State private var isEditing: Bool = false
@State private var tipsShown: Bool = false
@State private var siriTipShown: Bool = false
fileprivate let itemSpacing: CGFloat = 10.0
var body: some View {
let columns: [GridItem] = self._columns()
let timers: [AbstractTimer] = Array(self.timers)
Group {
if timers.count > 0 {
VStack {
TimersView(isEditing: self.$isEditing)
.environment(\.managedObjectContext, viewContext)
.environmentObject(self.boringContext)
// if !self.tipsShown, let tip = Preferences.tipToShow {
// TipView(tip: tip) {
// self._hideTip(tip)
// }.padding()
// }
Spacer()
SiriTimerView(timer: self.boringContext.siriTimer)
if !conductor.liveTimers.isEmpty {
GeometryReader { reader in
let width: CGFloat = reader.size.width / CGFloat(columns.count) - 15.0
VStack {
ScrollView {
LazyVGrid(
columns: columns,
spacing: itemSpacing
) {
ReorderableForEach(items: timers) { timer in
DialView(timer: timer, isEditingBinding: self.$isEditing, frameSize: width, handler: { id in
self._handleSiriTips(timerId: id)
})
.environment(\.managedObjectContext, viewContext)
.environmentObject(Conductor.maestro)
.environmentObject(boringContext)
} moveAction: { from, to in
self._reorder(from: from, to: to)
}
}
}.padding(.horizontal, itemSpacing)
// if !self.tipsShown, let tip = Preferences.tipToShow {
// TipView(tip: tip) {
// self._hideTip(tip)
// }.padding()
// }
Spacer()
SiriTipView(intent: StartTimerIntent(), isVisible: self.$siriTipShown)
.siriTipViewStyle(SiriTipViewStyle.dark).padding()
if !conductor.liveTimers.isEmpty {
HStack(alignment: .center) {
VolumeView().padding(12.0)
}.frame(width: 300.0, height: 40.0)
.background(Color(white: 0.9))
.cornerRadius(16.0)
LiveTimerListView()
.environment(\.managedObjectContext, viewContext)
.environmentObject(conductor)
.padding(.horizontal, 12.0)
.foregroundColor(.white)
.background(Color(white: 0.1))
.cornerRadius(32.0, corners: [.topRight, .topLeft])
}
}
}
} else {
Text("You'll find your timers here. Start by creating them on the left screen")
HStack(alignment: .center) {
VolumeView().padding(12.0)
}.frame(width: 300.0, height: 40.0)
.background(Color(white: 0.9))
.cornerRadius(16.0)
LiveTimerListView()
.environment(\.managedObjectContext, viewContext)
.environmentObject(conductor)
.padding(.horizontal, 12.0)
.foregroundColor(.white)
.background(Color(white: 0.1))
.cornerRadius(32.0, corners: [.topRight, .topLeft])
}
}
.navigationTitle("Home")
.alert(boringContext.error?.localizedDescription ?? "missing error", isPresented: $boringContext.showDefaultAlert) {
Button("OK", role: .cancel) { }
@ -159,22 +120,6 @@ struct ContentView<T : AbstractTimer>: View {
}
fileprivate func _columnCount() -> Int {
#if os(iOS)
if UIDevice.isPhoneIdiom {
return 2
} else {
return 3
}
#else
return 3
#endif
}
fileprivate func _columns() -> [GridItem] {
return (0..<self._columnCount()).map { _ in GridItem(spacing: 10.0) }
}
// MARK: - Business
fileprivate func _hideTip(_ tip: Tip) {
@ -182,33 +127,12 @@ struct ContentView<T : AbstractTimer>: View {
self.tipsShown = true
}
fileprivate func _reorder(from: IndexSet, to: Int) {
var timers: [AbstractTimer] = Array(self.timers)
timers.move(fromOffsets: from, toOffset: to)
for (i, countdown) in timers.enumerated() {
countdown.order = Int16(i)
}
do {
try viewContext.save()
} catch {
Logger.error(error)
self.boringContext.error = error
}
}
fileprivate func _askPermissions() {
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound]) { success, error in
print("requestAuthorization > success = \(success), error = \(String(describing: error))")
}
}
fileprivate func _handleSiriTips(timerId: String) {
if !Preferences.timerSiriTips.contains(timerId) {
self.siriTipShown = true
Preferences.timerSiriTips.insert(timerId)
}
}
fileprivate func _performActionIfPossible(url: URL) {
// hide new window if launching a timer

@ -17,11 +17,9 @@ struct DialView: View {
@State var timer: AbstractTimer
var isEditingBinding: Binding<Bool>
// @State var showSilentModeAlert: Bool = false
var frameSize: CGFloat
var handler: ((String) -> ())? = nil
var handler: ((AbstractTimer) -> ())? = nil
var body: some View {
ZStack {
@ -34,12 +32,6 @@ struct DialView: View {
case false:
Button {
self._launchTimer()
// if Preferences.hideSilentModeAlerts {
// self._launchTimer()
// } else {
// self.showSilentModeAlert = true
// }
} label: {
VStack {
Spacer()
@ -133,7 +125,7 @@ struct DialView: View {
fileprivate func _launchTimer() {
self.handler?(self.timer.stringId)
self.handler?(self.timer)
TimerRouter.performAction(timer: self.timer) { result in
switch result {

@ -0,0 +1,40 @@
//
// SiriTimerView.swift
// LeCountdown
//
// Created by Laurent Morvillier on 10/03/2023.
//
import SwiftUI
import _AppIntents_SwiftUI
struct SiriTimerView: View {
var timer: AbstractTimer? = nil
@State var isVisible = true
var body: some View {
if self.timer != nil {
SiriTipView(intent: self.intent(), isVisible: self.$isVisible)
.siriTipViewStyle(SiriTipViewStyle.dark).padding()
} else {
Text("no siri view")
}
}
fileprivate func intent() -> StartTimerIntent {
let intent = StartTimerIntent()
if let timer {
intent.timer = TimerIdentifierAppEntity(id: timer.stringId, displayString: timer.displayName)
}
return intent
}
}
struct SiriTimerView_Previews: PreviewProvider {
static var previews: some View {
SiriTimerView()
}
}

@ -0,0 +1,107 @@
//
// TimersView.swift
// LeCountdown
//
// Created by Laurent Morvillier on 10/03/2023.
//
import SwiftUI
struct TimersView: View {
@EnvironmentObject var boringContext: BoringContext
@Environment(\.managedObjectContext) private var viewContext
@Binding var isEditing: Bool
@FetchRequest(
sortDescriptors: [NSSortDescriptor(keyPath: \AbstractTimer.order, ascending: true)],
animation: .default)
private var timers: FetchedResults<AbstractTimer>
fileprivate let itemSpacing: CGFloat = 10.0
var body: some View {
GeometryReader { reader in
let columns: [GridItem] = self._columns()
let width: CGFloat = reader.size.width / CGFloat(columns.count) - 15.0
let abstractTimers: [AbstractTimer] = Array(self.timers)
if abstractTimers.count > 0 {
ScrollView {
LazyVGrid(
columns: columns,
spacing: itemSpacing
) {
ReorderableForEach(items: abstractTimers) { timer in
DialView(timer: timer, isEditingBinding: self.$isEditing, frameSize: width, handler: { timer in
self._handleSiriTips(timer: timer)
})
.environment(\.managedObjectContext, viewContext)
.environmentObject(Conductor.maestro)
.environmentObject(boringContext)
} moveAction: { from, to in
self._reorder(from: from, to: to)
}
}
}
} else {
Text("You'll find your timers here. Start by creating them on the left screen")
}
}.padding(.horizontal, itemSpacing)
}
fileprivate func _reorder(from: IndexSet, to: Int) {
var timers: [AbstractTimer] = Array(self.timers)
timers.move(fromOffsets: from, toOffset: to)
for (i, countdown) in timers.enumerated() {
countdown.order = Int16(i)
}
do {
try viewContext.save()
} catch {
Logger.error(error)
self.boringContext.error = error
}
}
fileprivate func _handleSiriTips(timer: AbstractTimer) {
let timerId = timer.stringId
if !Preferences.timerSiriTips.contains(timerId) {
self.boringContext.siriTimer = timer
Preferences.timerSiriTips.insert(timerId)
}
}
fileprivate func _columnCount() -> Int {
#if os(iOS)
if UIDevice.isPhoneIdiom {
return 2
} else {
return 3
}
#else
return 3
#endif
}
fileprivate func _columns() -> [GridItem] {
return (0..<self._columnCount()).map { _ in GridItem(spacing: 10.0) }
}
}
struct TimersView_Previews: PreviewProvider {
static var previews: some View {
TimersView(isEditing: .constant(false))
}
}
Loading…
Cancel
Save