Navigation changes

main
Laurent 3 years ago
parent b4ef494280
commit d05483d117
  1. 17
      LeCountdown/Views/ContentView.swift
  2. 55
      LeCountdown/Views/Countdown/CountdownFormView.swift
  3. 81
      LeCountdown/Views/Countdown/NewCountdownView.swift
  4. 54
      LeCountdown/Views/PresetsView.swift
  5. 38
      LeCountdown/Views/Reusable/SoundFormView.swift
  6. 7
      LeCountdown/Views/Reusable/TimerModel.swift
  7. 4
      LeCountdown/Views/Reusable/View+Extension.swift
  8. 3
      LeCountdown/Views/TimersView.swift
  9. 2
      LeCountdown/en.lproj/Localizable.strings
  10. 2
      LeCountdown/fr.lproj/Localizable.strings

@ -129,7 +129,8 @@ struct ContentView<T : AbstractTimer>: View {
}) })
.sheet(isPresented: self.$showAddSheet, content: { .sheet(isPresented: self.$showAddSheet, content: {
NavigationStack { NavigationStack {
PresetsView(tabSelection: .constant(0)) NewCountdownView(isPresented: $showAddSheet, tabSelection: .constant(0))
.environment(\.managedObjectContext, viewContext)
} }
}) })
.toolbar { .toolbar {
@ -143,26 +144,28 @@ struct ContentView<T : AbstractTimer>: View {
} }
} }
ToolbarItemGroup(placement: .navigationBarTrailing) { ToolbarItemGroup(placement: .navigationBarTrailing) {
if self.viewContext.count(entityName: "Record") > 0 {
Button { Button {
withAnimation { withAnimation {
self.showSettingsSheet.toggle() self.showStatsSheet.toggle()
} }
} label: { } label: {
Image(systemName: "gearshape.fill") Image(systemName: "chart.bar.doc.horizontal")
}
} }
Button { Button {
withAnimation { withAnimation {
self.showAddSheet.toggle() self.showSettingsSheet.toggle()
} }
} label: { } label: {
Image(systemName: "plus") Image(systemName: "gearshape.fill")
} }
Button { Button {
withAnimation { withAnimation {
self.showStatsSheet.toggle() self.showAddSheet.toggle()
} }
} label: { } label: {
Image(systemName: "chart.bar.doc.horizontal") Image(systemName: "plus")
} }
} }
} }

@ -7,19 +7,17 @@
import SwiftUI import SwiftUI
enum CountdownField: Int, Hashable {
struct CountdownFormView : View { case name
enum CountdownField: Int, Hashable {
case minutes case minutes
case seconds case seconds
case name }
}
struct CountdownFormView : View {
@FocusState private var focusedField: CountdownField? @FocusState var focusedField: CountdownField?
@EnvironmentObject var model: TimerModel @EnvironmentObject var model: TimerModel
@EnvironmentObject var confirmationModel: TimerModel
var secondsBinding: Binding<String> var secondsBinding: Binding<String>
var minutesBinding: Binding<String> var minutesBinding: Binding<String>
@ -29,12 +27,19 @@ struct CountdownFormView : View {
var repeatCountBinding: Binding<Int16> var repeatCountBinding: Binding<Int16>
// var textFieldIsFocused: FocusState<Bool>.Binding
var intervalRepeatBinding: Binding<Int>? = nil var intervalRepeatBinding: Binding<Int>? = nil
var body: some View { var body: some View {
Form {
Group {
Section("Name") {
TextField("Name", text: nameBinding)
.focused($focusedField, equals: .name)
.onSubmit {
self.focusNextField($focusedField)
}
}
Section("Duration") { Section("Duration") {
TextField("Minutes", text: minutesBinding) TextField("Minutes", text: minutesBinding)
.keyboardType(.numberPad) .keyboardType(.numberPad)
@ -45,11 +50,6 @@ struct CountdownFormView : View {
TextField("Seconds", text: secondsBinding) TextField("Seconds", text: secondsBinding)
.keyboardType(.numberPad) .keyboardType(.numberPad)
.focused($focusedField, equals: .seconds) .focused($focusedField, equals: .seconds)
.onSubmit {
self.focusNextField($focusedField)
}
TextField("Name (activates tracking)", text: nameBinding)
.focused($focusedField, equals: .name)
.onSubmit { .onSubmit {
self.focusedField = nil self.focusedField = nil
} }
@ -59,25 +59,7 @@ struct CountdownFormView : View {
model: self.model, model: self.model,
imageBinding: imageBinding, imageBinding: imageBinding,
repeatCountBinding: repeatCountBinding) repeatCountBinding: repeatCountBinding)
}.toolbar {
ToolbarItemGroup(placement: .keyboard) {
Button {
self.focusedField = nil
} label: {
Image(systemName: "keyboard.chevron.compact.down")
}
Spacer()
Button {
self.focusPreviousField($focusedField)
} label: {
Image(systemName: "chevron.up")
}
Button {
self.focusNextField($focusedField)
} label: {
Image(systemName: "chevron.down")
}
}
} }
} }
@ -89,6 +71,8 @@ struct CountdownFormView_Previews: PreviewProvider {
@FocusState static var textFieldIsFocused: Bool @FocusState static var textFieldIsFocused: Bool
static var previews: some View { static var previews: some View {
Form {
CountdownFormView( CountdownFormView(
secondsBinding: .constant(""), secondsBinding: .constant(""),
minutesBinding: .constant(""), minutesBinding: .constant(""),
@ -98,4 +82,5 @@ struct CountdownFormView_Previews: PreviewProvider {
intervalRepeatBinding: .constant(2)) intervalRepeatBinding: .constant(2))
.environmentObject(TimerModel()) .environmentObject(TimerModel())
} }
}
} }

@ -70,14 +70,18 @@ struct CountdownEditView : View {
// @FocusState private var textFieldIsFocused: Bool // @FocusState private var textFieldIsFocused: Bool
@FetchRequest(sortDescriptors: []) // @FetchRequest(sortDescriptors: [])
private var timers: FetchedResults<AbstractTimer> // private var timers: FetchedResults<AbstractTimer>
@State var _isNewCountdown: Bool = false // false if editing an existing countdown @State var _isNewCountdown: Bool = false // false if editing an existing countdown
@State var _hasLoaded = false @State var _hasLoaded = false
@Environment(\.isPresented) var envIsPresented @Environment(\.isPresented) var envIsPresented
@State var shouldScrollToTop: Bool = false
@FocusState private var focusedField: CountdownField?
init(isPresented: Binding<Bool>, countdown: Countdown? = nil, tabSelection: Binding<Int>? = nil) { init(isPresented: Binding<Bool>, countdown: Countdown? = nil, tabSelection: Binding<Int>? = nil) {
_isPresented = isPresented _isPresented = isPresented
self.countdown = countdown self.countdown = countdown
@ -90,8 +94,13 @@ struct CountdownEditView : View {
self.tabSelection = tabSelection self.tabSelection = tabSelection
} }
fileprivate var _formId = "formId"
var body: some View { var body: some View {
NavigationStack { NavigationStack {
ScrollViewReader { reader in
Rectangle() Rectangle()
.frame(width: 0.0, height: 0.0) .frame(width: 0.0, height: 0.0)
.onChange(of: envIsPresented) { newValue in .onChange(of: envIsPresented) { newValue in
@ -99,13 +108,53 @@ struct CountdownEditView : View {
self._save() // save when leaving an edit screen self._save() // save when leaving an edit screen
} }
} }
Form {
EmptyView().id("anchor")
CountdownFormView( CountdownFormView(
focusedField: _focusedField,
secondsBinding: $secondsString, secondsBinding: $secondsString,
minutesBinding: $minutesString, minutesBinding: $minutesString,
nameBinding: $nameString, nameBinding: $nameString,
imageBinding: $image, imageBinding: $image,
repeatCountBinding: $soundRepeatCount) repeatCountBinding: $soundRepeatCount)
.environmentObject(self.model) .environmentObject(self.model)
BasePresetsView { preset in
self._loadPreset(preset)
}
}.toolbar {
ToolbarItemGroup(placement: .keyboard) {
Button {
self.focusedField = nil
} label: {
Image(systemName: "keyboard.chevron.compact.down")
}
Spacer()
Button {
self.focusPreviousField($focusedField)
} label: {
Image(systemName: "chevron.up")
}
Button {
self.focusNextField($focusedField)
} label: {
Image(systemName: "chevron.down")
}
}
}
.onChange(of: self.shouldScrollToTop) { newValue in
withAnimation {
reader.scrollTo("anchor")
}
}
}
.navigationTitle("Edit countdown")
}
.onAppear { .onAppear {
self._onAppear() self._onAppear()
} }
@ -157,8 +206,6 @@ struct CountdownEditView : View {
} }
} }
} }
.navigationTitle("Edit countdown")
}
} }
@ -185,16 +232,20 @@ struct CountdownEditView : View {
let minutes = Int(preset.duration / 60.0) let minutes = Int(preset.duration / 60.0)
if minutes > 0 { if minutes > 0 {
self.minutesString = nf.string(from: NSNumber(value: minutes)) ?? "" self.minutesString = nf.string(from: NSNumber(value: minutes)) ?? ""
} else {
self.minutesString = ""
} }
let seconds = Int(preset.duration) - minutes * 60 let seconds = Int(preset.duration) - minutes * 60
if seconds > 0 { if seconds > 0 {
self.secondsString = nf.string(from: NSNumber(value: seconds)) ?? "" self.secondsString = nf.string(from: NSNumber(value: seconds)) ?? ""
} else {
self.secondsString = ""
} }
self.model.group = preset.intervalGroup self.model.group = preset.intervalGroup
self.model.soundModel.sounds = preset.sound self.model.soundModel.loadPreset(preset)
self.shouldScrollToTop.toggle()
} }
fileprivate func _loadCountdown(_ countdown: Countdown) { fileprivate func _loadCountdown(_ countdown: Countdown) {
@ -235,7 +286,7 @@ struct CountdownEditView : View {
} }
fileprivate func _cancel() { fileprivate func _cancel() {
viewContext.rollback() self.viewContext.rollback()
self.isPresented = false self.isPresented = false
} }
@ -251,11 +302,20 @@ struct CountdownEditView : View {
cd.duration = self._minutes * 60.0 + self._seconds cd.duration = self._minutes * 60.0 + self._seconds
if self._isNewCountdown { if self._isNewCountdown {
let max: Int16 let max: Int16
if let maxOrder = self.timers.map({ $0.order }).max() {
do {
let request = AbstractTimer.fetchRequest()
let timers = try viewContext.fetch(request)
if let maxOrder = timers.map({ $0.order }).max() {
max = maxOrder + 1 max = maxOrder + 1
} else { } else {
max = 0 max = 0
} }
} catch {
max = 0
}
cd.order = max cd.order = max
} }
@ -303,9 +363,7 @@ struct CountdownEditView : View {
dismiss() dismiss()
} }
// if self.preset != nil {
self.tabSelection?.wrappedValue = 1 self.tabSelection?.wrappedValue = 1
// }
} }
@ -340,7 +398,8 @@ struct CountdownEditView : View {
struct NewCountdownView_Previews: PreviewProvider { struct NewCountdownView_Previews: PreviewProvider {
static var previews: some View { static var previews: some View {
NewCountdownView(isPresented: .constant(true), tabSelection: .constant(0)) NewCountdownView(isPresented: .constant(true),
tabSelection: .constant(0))
.environment(\.managedObjectContext, PersistenceController.preview.container.viewContext) .environment(\.managedObjectContext, PersistenceController.preview.container.viewContext)
} }
} }

@ -9,7 +9,41 @@ import SwiftUI
class PresetModel : ObservableObject { class PresetModel : ObservableObject {
@Published var selectedPreset: Preset = Preset.hardBoiledEggs @Published var selectedPreset: Preset = Preset.nap
}
struct BasePresetsView: View {
var handler: (Preset) -> ()
var body: some View {
Section("Preselections") {
ForEach(PresetSection.allCases) { section in
ForEach(section.presets) { preset in
Button {
self.handler(preset)
} label: {
TimerItemView(name: preset.localizedName,
duration: preset.formattedDuration,
sound: preset.soundTitle)
}
}
}
}
}
fileprivate func _columnCount() -> Int {
return 2
}
fileprivate func _columns() -> [GridItem] {
return (0..<self._columnCount()).map { _ in GridItem(spacing: 10.0) }
}
} }
@ -137,18 +171,18 @@ struct TimerItemView: View {
var body: some View { var body: some View {
HStack { HStack {
VStack(alignment: .leading) { VStack(alignment: .leading) {
Text(self.name.uppercased()) Text(self.name.uppercased()).foregroundColor(.black)
Text(self.duration) Text(self.duration)
.foregroundColor(Color.accentColor) .foregroundColor(Color.accentColor)
Text(self.sound.uppercased()).foregroundColor(Color(white: 0.7)) Text(self.sound.uppercased()).foregroundColor(Color(white: 0.7))
}.padding() }//.padding()
.multilineTextAlignment(.leading) .multilineTextAlignment(.leading)
Spacer() Spacer()
}.background(Color(white: 0.1)) }//.background(Color(white: 0.1))
.cornerRadius(16.0) //.cornerRadius(16.0)
.monospaced() .monospaced()
.font(Font.system(size: 16.0, weight: .semibold)) .font(Font.system(size: 16.0))
.foregroundColor(Color.white) //.foregroundColor(Color.white)
} }
} }
@ -262,9 +296,9 @@ enum Preset: Int, Identifiable, CaseIterable {
} }
} }
var sound: Set<Sound> { // var sounds: Set<Sound> {
return Set(SoundCatalog.main.sounds(for: self.playlist)) // return Set(SoundCatalog.main.sounds(for: self.playlist))
} // }
var formattedDuration: String { var formattedDuration: String {
let group = self.intervalGroup let group = self.intervalGroup

@ -20,9 +20,7 @@ struct SoundFormView : View {
var body: some View { var body: some View {
Group { Section("Properties") {
Section(header: Text("Properties")) {
if self.optionalSound != nil { if self.optionalSound != nil {
Toggle("Play sound on end", isOn: optionalSound!) Toggle("Play sound on end", isOn: optionalSound!)
@ -33,33 +31,12 @@ struct SoundFormView : View {
Text("Sound") Text("Sound")
} }
} }
} else {
// Picker(selection: soundBinding) {
// ForEach(Sound.allCases) { sound in
// Text(sound.localizedString).tag(sound)
// }
// } label: {
// Text("Sound")
// }
} }
SoundLinkView(soundModel: self.model.soundModel, SoundLinkView(soundModel: self.model.soundModel,
catalog: .ring, catalog: .ring,
title: "Sound") title: "Sound")
// NavigationLink {
// NavigationStack {
// PlaylistsView(model: self.model.soundModel, catalog: .ring)
// }
// } label: {
// HStack {
// Text("Sound")
// Spacer()
// Text(self.model.soundModel.soundSelection)
// }
// }
if self.repeatCountBinding != nil { if self.repeatCountBinding != nil {
Picker("Repeat Count", selection: self.repeatCountBinding!) { Picker("Repeat Count", selection: self.repeatCountBinding!) {
ForEach(0..<6) { ForEach(0..<6) {
@ -74,19 +51,6 @@ struct SoundFormView : View {
catalog: .confirmation, catalog: .confirmation,
title: "Confirmation Sound") title: "Confirmation Sound")
// NavigationLink {
// NavigationStack {
// PlaylistsView(model: self.model.confirmationSoundModel, catalog: .confirmation)
// }
// } label: {
// HStack {
// Text("Confirmation Sound")
// Spacer()
// Text(self.confirmationModel.soundSelection)
// }
// }
}
}.sheet(isPresented: self.$imageSelectionSheetShown) { }.sheet(isPresented: self.$imageSelectionSheetShown) {
ImageSelectionView(showBinding: self.$imageSelectionSheetShown, imageBinding: self.imageBinding) ImageSelectionView(showBinding: self.$imageSelectionSheetShown, imageBinding: self.imageBinding)
} }

@ -32,6 +32,12 @@ class SoundModel: ObservableObject, SoundHolder {
} }
} }
func loadPreset(_ preset: Preset) {
self.playlists.removeAll()
self.sounds.removeAll()
self.selectPlaylist(preset.playlist, selected: true)
}
func setPlayables(_ playables: [any Playable]) { func setPlayables(_ playables: [any Playable]) {
for playable in playables { for playable in playables {
switch playable { switch playable {
@ -104,7 +110,6 @@ class SoundModel: ObservableObject, SoundHolder {
} }
func selectPlaylist(_ playlist: Playlist, selected: Bool) { func selectPlaylist(_ playlist: Playlist, selected: Bool) {
let sounds = SoundCatalog.main.sounds(for: playlist) let sounds = SoundCatalog.main.sounds(for: playlist)
if selected { if selected {
self.playlists.insert(playlist) self.playlists.insert(playlist)

@ -30,6 +30,8 @@ extension View {
let nextValue = currentValue.rawValue + 1 let nextValue = currentValue.rawValue + 1
if let newValue = F.init(rawValue: nextValue) { if let newValue = F.init(rawValue: nextValue) {
field.wrappedValue = newValue field.wrappedValue = newValue
} else {
field.wrappedValue = nil
} }
} }
@ -54,6 +56,8 @@ extension View {
let nextValue = currentValue.rawValue - 1 let nextValue = currentValue.rawValue - 1
if let newValue = F.init(rawValue: nextValue) { if let newValue = F.init(rawValue: nextValue) {
field.wrappedValue = newValue field.wrappedValue = newValue
} else {
field.wrappedValue = nil
} }
} }
} }

@ -68,7 +68,8 @@ struct TimersView: View {
} }
.padding(.horizontal, itemSpacing) .padding(.horizontal, itemSpacing)
} else { } else {
Text("You'll find your timers here. Start by creating them on the left screen").multilineTextAlignment(.center) Text("You'll find your timers here. Start by creating them on the left screen")
.multilineTextAlignment(.center)
} }
} }

@ -1 +1 @@
"You'll find your timers here. Start by creating them on the left screen" = "You'll find your timers here.\nStart by creating them on the left screen"; "You'll find your timers here. Start by creating them on the left screen" = "You'll find your timers here.\nStart by creating using the top right button";

@ -247,7 +247,7 @@
"Timer %@ started" = "Le minuteur %@ a démarré"; "Timer %@ started" = "Le minuteur %@ a démarré";
"The timer has not been found in the app" = "Le minuteur n'a pas été trouvé dans l'app"; "The timer has not been found in the app" = "Le minuteur n'a pas été trouvé dans l'app";
"In-app purchase disabled" = "Les achats in-app sont désactivés. Veuillez les activer si vous souhaitez vous abonner"; "In-app purchase disabled" = "Les achats in-app sont désactivés. Veuillez les activer si vous souhaitez vous abonner";
"You'll find your timers here. Start by creating them on the left screen" = "Vous retrouverez vos minuteurs ici.\nCommencez par en créer dans l'écran de gauche"; "You'll find your timers here. Start by creating them on the left screen" = "Vous retrouverez vos minuteurs ici.\nCommencez par en créer un avec le bouton en haut à gauche";
"Settings" = "Réglages"; "Settings" = "Réglages";
"Pasta" = "Pasta"; "Pasta" = "Pasta";
"Rice" = "Riz"; "Rice" = "Riz";

Loading…
Cancel
Save