From b3447d2f00d3d418e20434d6ec7dec4f73e6e155 Mon Sep 17 00:00:00 2001 From: Laurent Date: Thu, 23 Nov 2023 14:34:21 +0100 Subject: [PATCH] adds interval UI and db changes --- LeCountdown.xcodeproj/project.pbxproj | 84 ++++++++--------- .../Countdown+CoreDataProperties.swift | 21 ++++- .../Generation/Interval+CoreDataClass.swift | 15 ---- .../Interval+CoreDataProperties.swift | 27 ------ .../IntervalGroup+CoreDataClass.swift | 15 ---- .../IntervalGroup+CoreDataProperties.swift | 44 --------- .../Generation/TimeRange+CoreDataClass.swift | 15 ++++ .../TimeRange+CoreDataProperties.swift | 29 ++++++ .../.xccurrentversion | 2 +- .../LeCountdown.0.6.6.xcdatamodel/contents | 50 +++++++++++ LeCountdown/Model/Model+Extensions.swift | 10 +++ .../Model/Model+SharedExtensions.swift | 4 + .../Views/Countdown/CountdownFormView.swift | 77 +++++++++++++--- .../Views/Countdown/NewCountdownView.swift | 75 +++++++++------- .../Views/Countdown/RangeFormView.swift | 67 ++++++++++++++ LeCountdown/Views/NewDataView.swift | 82 ++++++++--------- LeCountdown/Views/PresetsView.swift | 38 ++++---- LeCountdown/Views/Reusable/TimerModel.swift | 15 +++- LeCountdown/Views/StartView.swift | 89 +++++++++++-------- TimeRange+CoreDataClass.swift | 15 ++++ TimeRange+CoreDataProperties.swift | 29 ++++++ 21 files changed, 513 insertions(+), 290 deletions(-) delete mode 100644 LeCountdown/Model/Generation/Interval+CoreDataClass.swift delete mode 100644 LeCountdown/Model/Generation/Interval+CoreDataProperties.swift delete mode 100644 LeCountdown/Model/Generation/IntervalGroup+CoreDataClass.swift delete mode 100644 LeCountdown/Model/Generation/IntervalGroup+CoreDataProperties.swift create mode 100644 LeCountdown/Model/Generation/TimeRange+CoreDataClass.swift create mode 100644 LeCountdown/Model/Generation/TimeRange+CoreDataProperties.swift create mode 100644 LeCountdown/Model/LeCountdown.xcdatamodeld/LeCountdown.0.6.6.xcdatamodel/contents create mode 100644 LeCountdown/Views/Countdown/RangeFormView.swift create mode 100644 TimeRange+CoreDataClass.swift create mode 100644 TimeRange+CoreDataProperties.swift diff --git a/LeCountdown.xcodeproj/project.pbxproj b/LeCountdown.xcodeproj/project.pbxproj index 7202d67..9723771 100644 --- a/LeCountdown.xcodeproj/project.pbxproj +++ b/LeCountdown.xcodeproj/project.pbxproj @@ -131,6 +131,7 @@ C47C933629F01B6600C780E2 /* FileUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4556F6E29E40BED00DEB40B /* FileUtils.swift */; }; C47C933729F01B7A00C780E2 /* Codable+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4556F7029E40DCF00DEB40B /* Codable+Extensions.swift */; }; C47C933929F13BD100C780E2 /* AppleMusicPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C47C933829F13BD100C780E2 /* AppleMusicPickerView.swift */; }; + C48920672B0E57C900F6F4D8 /* RangeFormView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C48920662B0E57C900F6F4D8 /* RangeFormView.swift */; }; C48940DE2AC307860086F4FA /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = C48940DD2AC307860086F4FA /* GoogleService-Info.plist */; }; C48940DF2AC307860086F4FA /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = C48940DD2AC307860086F4FA /* GoogleService-Info.plist */; }; C48940E02AC307860086F4FA /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = C48940DD2AC307860086F4FA /* GoogleService-Info.plist */; }; @@ -191,18 +192,6 @@ C4BA2AFD299A3A3700CB4FBA /* AppleMusicPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BA2AFC299A3A3700CB4FBA /* AppleMusicPlayer.swift */; }; C4BA2B04299A42EF00CB4FBA /* NewDataView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BA2B03299A42EF00CB4FBA /* NewDataView.swift */; }; C4BA2B06299A8F8D00CB4FBA /* PresetsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BA2B05299A8F8D00CB4FBA /* PresetsView.swift */; }; - C4BA2B0F299BE61E00CB4FBA /* Interval+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BA2B0A299BE61E00CB4FBA /* Interval+CoreDataClass.swift */; }; - C4BA2B10299BE61E00CB4FBA /* Interval+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BA2B0B299BE61E00CB4FBA /* Interval+CoreDataProperties.swift */; }; - C4BA2B11299BE61E00CB4FBA /* IntervalGroup+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BA2B0C299BE61E00CB4FBA /* IntervalGroup+CoreDataClass.swift */; }; - C4BA2B13299BE61E00CB4FBA /* Countdown+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BA2B0E299BE61E00CB4FBA /* Countdown+CoreDataProperties.swift */; }; - C4BA2B15299BE6A000CB4FBA /* Interval+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BA2B0B299BE61E00CB4FBA /* Interval+CoreDataProperties.swift */; }; - C4BA2B16299BE6A000CB4FBA /* IntervalGroup+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BA2B0C299BE61E00CB4FBA /* IntervalGroup+CoreDataClass.swift */; }; - C4BA2B18299BE6A000CB4FBA /* Interval+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BA2B0A299BE61E00CB4FBA /* Interval+CoreDataClass.swift */; }; - C4BA2B19299BE6A000CB4FBA /* Countdown+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BA2B0E299BE61E00CB4FBA /* Countdown+CoreDataProperties.swift */; }; - C4BA2B1B299BE6A100CB4FBA /* Interval+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BA2B0B299BE61E00CB4FBA /* Interval+CoreDataProperties.swift */; }; - C4BA2B1C299BE6A100CB4FBA /* IntervalGroup+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BA2B0C299BE61E00CB4FBA /* IntervalGroup+CoreDataClass.swift */; }; - C4BA2B1E299BE6A100CB4FBA /* Interval+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BA2B0A299BE61E00CB4FBA /* Interval+CoreDataClass.swift */; }; - C4BA2B1F299BE6A100CB4FBA /* Countdown+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BA2B0E299BE61E00CB4FBA /* Countdown+CoreDataProperties.swift */; }; C4BA2B22299BE82E00CB4FBA /* AbstractSoundTimer+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BA2AEF2996A11900CB4FBA /* AbstractSoundTimer+CoreDataProperties.swift */; }; C4BA2B23299BE82E00CB4FBA /* AbstractSoundTimer+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BA2AEF2996A11900CB4FBA /* AbstractSoundTimer+CoreDataProperties.swift */; }; C4BA2B25299D35C100CB4FBA /* HomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BA2B24299D35C100CB4FBA /* HomeView.swift */; }; @@ -217,9 +206,6 @@ C4BA2B3C299F838000CB4FBA /* Model+SharedExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BA2B39299F838000CB4FBA /* Model+SharedExtensions.swift */; }; C4BA2B3E299FC86800CB4FBA /* StatisticsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BA2B3D299FC86800CB4FBA /* StatisticsView.swift */; }; C4BA2B43299FCB2B00CB4FBA /* RecordsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BA2B42299FCB2B00CB4FBA /* RecordsView.swift */; }; - C4BA2B49299FCE0C00CB4FBA /* IntervalGroup+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BA2B47299FCE0C00CB4FBA /* IntervalGroup+CoreDataProperties.swift */; }; - C4BA2B4A299FCE0C00CB4FBA /* IntervalGroup+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BA2B47299FCE0C00CB4FBA /* IntervalGroup+CoreDataProperties.swift */; }; - C4BA2B4B299FCE0C00CB4FBA /* IntervalGroup+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BA2B47299FCE0C00CB4FBA /* IntervalGroup+CoreDataProperties.swift */; }; C4BA2B4C299FCE0C00CB4FBA /* Stopwatch+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BA2B48299FCE0C00CB4FBA /* Stopwatch+CoreDataProperties.swift */; }; C4BA2B4D299FCE0C00CB4FBA /* Stopwatch+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BA2B48299FCE0C00CB4FBA /* Stopwatch+CoreDataProperties.swift */; }; C4BA2B4E299FCE0C00CB4FBA /* Stopwatch+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BA2B48299FCE0C00CB4FBA /* Stopwatch+CoreDataProperties.swift */; }; @@ -236,6 +222,15 @@ C4BA2B7329A60CF000CB4FBA /* Shortcut.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BA2B7229A60CF000CB4FBA /* Shortcut.swift */; }; C4BA2B7929A65C1400CB4FBA /* UIDevice+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BA2B7829A65C1400CB4FBA /* UIDevice+Extensions.swift */; }; C4BCABB92A040B97009FFB0A /* QP01 0023 Surf moderate sandy.wav in Resources */ = {isa = PBXBuildFile; fileRef = C4BCABB82A040B97009FFB0A /* QP01 0023 Surf moderate sandy.wav */; }; + C4C826612B0E411A0036C666 /* TimeRange+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8265D2B0E411A0036C666 /* TimeRange+CoreDataClass.swift */; }; + C4C826692B0E41D20036C666 /* TimeRange+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C826672B0E41D20036C666 /* TimeRange+CoreDataProperties.swift */; }; + C4C8266A2B0E41D20036C666 /* TimeRange+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C826672B0E41D20036C666 /* TimeRange+CoreDataProperties.swift */; }; + C4C8266B2B0E41D20036C666 /* TimeRange+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C826672B0E41D20036C666 /* TimeRange+CoreDataProperties.swift */; }; + C4C8266C2B0E41D20036C666 /* Countdown+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C826682B0E41D20036C666 /* Countdown+CoreDataProperties.swift */; }; + C4C8266D2B0E41D20036C666 /* Countdown+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C826682B0E41D20036C666 /* Countdown+CoreDataProperties.swift */; }; + C4C8266E2B0E41D20036C666 /* Countdown+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C826682B0E41D20036C666 /* Countdown+CoreDataProperties.swift */; }; + C4C8266F2B0E41DB0036C666 /* TimeRange+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8265D2B0E411A0036C666 /* TimeRange+CoreDataClass.swift */; }; + C4C826702B0E41DB0036C666 /* TimeRange+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8265D2B0E411A0036C666 /* TimeRange+CoreDataClass.swift */; }; C4E5D66629B73AED008E7465 /* StartTimerIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E5D66429B73AED008E7465 /* StartTimerIntent.swift */; }; C4E5D66729B73AED008E7465 /* TimerIdentifierAppEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E5D66529B73AED008E7465 /* TimerIdentifierAppEntity.swift */; }; C4E5D66A29B73FC6008E7465 /* TimerShortcuts.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E5D66929B73FC6008E7465 /* TimerShortcuts.swift */; }; @@ -438,6 +433,7 @@ C47A9AF22AD1B32C00618A50 /* URLs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLs.swift; sourceTree = ""; }; C47C933829F13BD100C780E2 /* AppleMusicPickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppleMusicPickerView.swift; sourceTree = ""; }; C47C933C29F13DBD00C780E2 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; + C48920662B0E57C900F6F4D8 /* RangeFormView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RangeFormView.swift; sourceTree = ""; }; C48940DD2AC307860086F4FA /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; C498E59E298D4DEA00E90DE0 /* LiveTimerListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveTimerListView.swift; sourceTree = ""; }; C498E5A0298D543900E90DE0 /* LiveTimer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveTimer.swift; sourceTree = ""; }; @@ -472,10 +468,6 @@ C4BA2B03299A42EF00CB4FBA /* NewDataView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewDataView.swift; sourceTree = ""; }; C4BA2B05299A8F8D00CB4FBA /* PresetsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PresetsView.swift; sourceTree = ""; }; C4BA2B07299BDAE000CB4FBA /* LeCountdown.0.6.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = LeCountdown.0.6.xcdatamodel; sourceTree = ""; }; - C4BA2B0A299BE61E00CB4FBA /* Interval+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Interval+CoreDataClass.swift"; sourceTree = ""; }; - C4BA2B0B299BE61E00CB4FBA /* Interval+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Interval+CoreDataProperties.swift"; sourceTree = ""; }; - C4BA2B0C299BE61E00CB4FBA /* IntervalGroup+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "IntervalGroup+CoreDataClass.swift"; sourceTree = ""; }; - C4BA2B0E299BE61E00CB4FBA /* Countdown+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Countdown+CoreDataProperties.swift"; sourceTree = ""; }; C4BA2B24299D35C100CB4FBA /* HomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeView.swift; sourceTree = ""; }; C4BA2B2C299E2DEE00CB4FBA /* Preferences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Preferences.swift; sourceTree = ""; }; C4BA2B2E299E69A000CB4FBA /* View+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+Extension.swift"; sourceTree = ""; }; @@ -485,7 +477,6 @@ C4BA2B3D299FC86800CB4FBA /* StatisticsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatisticsView.swift; sourceTree = ""; }; C4BA2B42299FCB2B00CB4FBA /* RecordsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecordsView.swift; sourceTree = ""; }; C4BA2B46299FCD8B00CB4FBA /* LeCountdown.0.6.1.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = LeCountdown.0.6.1.xcdatamodel; sourceTree = ""; }; - C4BA2B47299FCE0C00CB4FBA /* IntervalGroup+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "IntervalGroup+CoreDataProperties.swift"; sourceTree = ""; }; C4BA2B48299FCE0C00CB4FBA /* Stopwatch+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Stopwatch+CoreDataProperties.swift"; sourceTree = ""; }; C4BA2B56299FFA4F00CB4FBA /* AppGuard.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppGuard.swift; sourceTree = ""; }; C4BA2B5A299FFAB000CB4FBA /* Logger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = ""; }; @@ -499,6 +490,12 @@ C4BA2B7229A60CF000CB4FBA /* Shortcut.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Shortcut.swift; sourceTree = ""; }; C4BA2B7829A65C1400CB4FBA /* UIDevice+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIDevice+Extensions.swift"; sourceTree = ""; }; C4BCABB82A040B97009FFB0A /* QP01 0023 Surf moderate sandy.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = "QP01 0023 Surf moderate sandy.wav"; sourceTree = ""; }; + C4C8265A2B0E40350036C666 /* LeCountdown.0.6.6.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = LeCountdown.0.6.6.xcdatamodel; sourceTree = ""; }; + C4C8265D2B0E411A0036C666 /* TimeRange+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TimeRange+CoreDataClass.swift"; sourceTree = ""; }; + C4C826632B0E41610036C666 /* Countdown+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Countdown+CoreDataProperties.swift"; sourceTree = ""; }; + C4C826642B0E41610036C666 /* TimeRange+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TimeRange+CoreDataProperties.swift"; sourceTree = ""; }; + C4C826672B0E41D20036C666 /* TimeRange+CoreDataProperties.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "TimeRange+CoreDataProperties.swift"; sourceTree = ""; }; + C4C826682B0E41D20036C666 /* Countdown+CoreDataProperties.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Countdown+CoreDataProperties.swift"; sourceTree = ""; }; C4E5D66429B73AED008E7465 /* StartTimerIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StartTimerIntent.swift; sourceTree = ""; }; C4E5D66529B73AED008E7465 /* TimerIdentifierAppEntity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimerIdentifierAppEntity.swift; sourceTree = ""; }; C4E5D66929B73FC6008E7465 /* TimerShortcuts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimerShortcuts.swift; sourceTree = ""; }; @@ -595,6 +592,7 @@ C438C7F329812BB200BF3EF9 /* LaunchIntents */, C438C7CF2981216200BF3EF9 /* Frameworks */, C4060DBD297AE73B003FAB80 /* Products */, + C48920632B0E422E00F6F4D8 /* Recovered References */, ); sourceTree = ""; }; @@ -830,6 +828,15 @@ path = Sound_Assets; sourceTree = ""; }; + C48920632B0E422E00F6F4D8 /* Recovered References */ = { + isa = PBXGroup; + children = ( + C4C826632B0E41610036C666 /* Countdown+CoreDataProperties.swift */, + C4C826642B0E41610036C666 /* TimeRange+CoreDataProperties.swift */, + ); + name = "Recovered References"; + sourceTree = ""; + }; C4A16DBC29D1A69200143D5E /* Shorts */ = { isa = PBXGroup; children = ( @@ -912,17 +919,15 @@ C4F8B170298AC234005C86A5 /* Alarm+CoreDataClass.swift */, C4F8B1AA298AC3A0005C86A5 /* Alarm+CoreDataProperties.swift */, C4F8B16C298AC234005C86A5 /* Countdown+CoreDataClass.swift */, - C4BA2B0E299BE61E00CB4FBA /* Countdown+CoreDataProperties.swift */, + C4C826682B0E41D20036C666 /* Countdown+CoreDataProperties.swift */, C4BA2AED2996A11900CB4FBA /* CustomSound+CoreDataClass.swift */, C4BA2AEE2996A11900CB4FBA /* CustomSound+CoreDataProperties.swift */, - C4BA2B0A299BE61E00CB4FBA /* Interval+CoreDataClass.swift */, - C4BA2B0B299BE61E00CB4FBA /* Interval+CoreDataProperties.swift */, - C4BA2B0C299BE61E00CB4FBA /* IntervalGroup+CoreDataClass.swift */, - C4BA2B47299FCE0C00CB4FBA /* IntervalGroup+CoreDataProperties.swift */, C4F8B16E298AC234005C86A5 /* Record+CoreDataClass.swift */, C4F8B16F298AC234005C86A5 /* Record+CoreDataProperties.swift */, C4286E9F2A1502FD0070D075 /* Stopwatch+CoreDataClass.swift */, C4BA2B48299FCE0C00CB4FBA /* Stopwatch+CoreDataProperties.swift */, + C4C8265D2B0E411A0036C666 /* TimeRange+CoreDataClass.swift */, + C4C826672B0E41D20036C666 /* TimeRange+CoreDataProperties.swift */, ); path = Generation; sourceTree = ""; @@ -933,6 +938,7 @@ C4742B58298411E800D5D950 /* CountdownFormView.swift */, C4F8B1B7298AC81D005C86A5 /* CountdownDialView.swift */, C4060DF6297AFEF2003FAB80 /* NewCountdownView.swift */, + C48920662B0E57C900F6F4D8 /* RangeFormView.swift */, ); path = Countdown; sourceTree = ""; @@ -1275,7 +1281,9 @@ C4BA2B36299F82FB00CB4FBA /* Fakes.swift in Sources */, C4556F7629E411A400DEB40B /* LogsView.swift in Sources */, C498E5A1298D543900E90DE0 /* LiveTimer.swift in Sources */, + C48920672B0E57C900F6F4D8 /* RangeFormView.swift in Sources */, C4BA2B6329A3C34600CB4FBA /* Stat.swift in Sources */, + C4C8266C2B0E41D20036C666 /* Countdown+CoreDataProperties.swift in Sources */, C415D3E229C0C0C20037B215 /* MailView.swift in Sources */, C438C80F29828B8600BF3EF9 /* ActivitiesView.swift in Sources */, C4E5D68029B8FD93008E7465 /* Store.swift in Sources */, @@ -1284,7 +1292,6 @@ C438C80D2982847300BF3EF9 /* CoreDataRequests.swift in Sources */, C4742B5F2984205000D5D950 /* ViewModifiers.swift in Sources */, C4F8B1D8298C0727005C86A5 /* TimerRouter.swift in Sources */, - C4BA2B13299BE61E00CB4FBA /* Countdown+CoreDataProperties.swift in Sources */, C419EEE52AC5AC0200A66BBB /* ViewStyles.swift in Sources */, C4F8B186298AC234005C86A5 /* Activity+CoreDataClass.swift in Sources */, C4BA2B57299FFA4F00CB4FBA /* AppGuard.swift in Sources */, @@ -1314,7 +1321,6 @@ C473C2F929A8DC0A0056B38A /* LaunchWidgetAttributes.swift in Sources */, C445FA922987CC8A0054D761 /* Sound.swift in Sources */, C4F8B1D0298BF2E2005C86A5 /* DialView.swift in Sources */, - C4BA2B11299BE61E00CB4FBA /* IntervalGroup+CoreDataClass.swift in Sources */, C4286EA12A1502FD0070D075 /* Stopwatch+CoreDataClass.swift in Sources */, C4BA2AFD299A3A3700CB4FBA /* AppleMusicPlayer.swift in Sources */, C4BA2B3A299F838000CB4FBA /* Model+SharedExtensions.swift in Sources */, @@ -1348,10 +1354,11 @@ C4060DF5297AE9A7003FAB80 /* TimeInterval+Extensions.swift in Sources */, C4F8B166298A9ABB005C86A5 /* SoundFormView.swift in Sources */, C4F8B17D298AC234005C86A5 /* Record+CoreDataProperties.swift in Sources */, + C4C826612B0E411A0036C666 /* TimeRange+CoreDataClass.swift in Sources */, + C4C826692B0E41D20036C666 /* TimeRange+CoreDataProperties.swift in Sources */, C4F8B184298AC234005C86A5 /* AbstractTimer+CoreDataClass.swift in Sources */, C4F8B17A298AC234005C86A5 /* Countdown+CoreDataClass.swift in Sources */, C42E970229E6B32B005B1B8C /* CalendarView.swift in Sources */, - C4BA2B0F299BE61E00CB4FBA /* Interval+CoreDataClass.swift in Sources */, C47A9AF32AD1B32C00618A50 /* URLs.swift in Sources */, C4F8B164298A9A92005C86A5 /* AlarmFormView.swift in Sources */, C4286EB02A1B75AB0070D075 /* BoringContext.swift in Sources */, @@ -1363,7 +1370,6 @@ C4E5D68A29BB7953008E7465 /* SettingsView.swift in Sources */, C4060DF7297AFEF2003FAB80 /* NewCountdownView.swift in Sources */, C4060DCC297AE73D003FAB80 /* LeCountdown.xcdatamodeld in Sources */, - C4BA2B49299FCE0C00CB4FBA /* IntervalGroup+CoreDataProperties.swift in Sources */, C4F8B17C298AC234005C86A5 /* Record+CoreDataClass.swift in Sources */, C4BA2B5B299FFAB000CB4FBA /* Logger.swift in Sources */, C4E5D66729B73AED008E7465 /* TimerIdentifierAppEntity.swift in Sources */, @@ -1374,7 +1380,6 @@ C4A16D9529C4B06400143D5E /* StatePlayer.swift in Sources */, C4BA2B6129A3C02400CB4FBA /* ActivityStatsView.swift in Sources */, C4BA2B6A29A4BE1800CB4FBA /* Date+Extensions.swift in Sources */, - C4BA2B10299BE61E00CB4FBA /* Interval+CoreDataProperties.swift in Sources */, C4F8B1AC298AC3A0005C86A5 /* Alarm+CoreDataProperties.swift in Sources */, C498E59F298D4DEA00E90DE0 /* LiveTimerListView.swift in Sources */, C4060DC0297AE73B003FAB80 /* LeCountdownApp.swift in Sources */, @@ -1411,13 +1416,12 @@ buildActionMask = 2147483647; files = ( C4A16D9829C4B06400143D5E /* StatePlayer.swift in Sources */, - C4BA2B15299BE6A000CB4FBA /* Interval+CoreDataProperties.swift in Sources */, C498E5A6299152C600E90DE0 /* GreenCheckmarkView.swift in Sources */, C4BA2B3B299F838000CB4FBA /* Model+SharedExtensions.swift in Sources */, C438C7EB2981266F00BF3EF9 /* SingleTimerView.swift in Sources */, + C4C826702B0E41DB0036C666 /* TimeRange+CoreDataClass.swift in Sources */, C47C933729F01B7A00C780E2 /* Codable+Extensions.swift in Sources */, C438C7D62981216200BF3EF9 /* LaunchWidgetBundle.swift in Sources */, - C4BA2B16299BE6A000CB4FBA /* IntervalGroup+CoreDataClass.swift in Sources */, C4286EAE2A17753A0070D075 /* AppError.swift in Sources */, C4F8B18B298AC288005C86A5 /* Record+CoreDataClass.swift in Sources */, C4F8B195298AC288005C86A5 /* Activity+CoreDataClass.swift in Sources */, @@ -1426,9 +1430,9 @@ C4BA2AF72996A4EF00CB4FBA /* CustomSound+CoreDataClass.swift in Sources */, C4F8B1AD298AC451005C86A5 /* AbstractSoundTimer+CoreDataClass.swift in Sources */, C445FA87298448730054D761 /* CoolPic.swift in Sources */, + C4C8266D2B0E41D20036C666 /* Countdown+CoreDataProperties.swift in Sources */, C47C933529F01B5E00C780E2 /* FileLogger.swift in Sources */, C438C8162982BE1E00BF3EF9 /* LeCountdown.xcdatamodeld in Sources */, - C4BA2B4A299FCE0C00CB4FBA /* IntervalGroup+CoreDataProperties.swift in Sources */, C4BA2B4D299FCE0C00CB4FBA /* Stopwatch+CoreDataProperties.swift in Sources */, C4F8B194298AC288005C86A5 /* Record+CoreDataProperties.swift in Sources */, C438C8152982BD9000BF3EF9 /* IntentDataProvider.swift in Sources */, @@ -1439,7 +1443,6 @@ C438C7E82981255D00BF3EF9 /* TimeInterval+Extensions.swift in Sources */, C4BA2B37299F82FF00CB4FBA /* Fakes.swift in Sources */, C4F8B18F298AC288005C86A5 /* AbstractTimer+CoreDataClass.swift in Sources */, - C4BA2B19299BE6A000CB4FBA /* Countdown+CoreDataProperties.swift in Sources */, C438C7D82981216200BF3EF9 /* LaunchWidgetLiveActivity.swift in Sources */, C4F8B18C298AC288005C86A5 /* Alarm+CoreDataClass.swift in Sources */, C438C8192982BFDB00BF3EF9 /* NSManagedContext+Extensions.swift in Sources */, @@ -1447,10 +1450,10 @@ C4BA2AF62996A4EF00CB4FBA /* CustomSound+CoreDataProperties.swift in Sources */, C4F8B192298AC288005C86A5 /* Activity+CoreDataProperties.swift in Sources */, C47C933629F01B6600C780E2 /* FileUtils.swift in Sources */, - C4BA2B18299BE6A000CB4FBA /* Interval+CoreDataClass.swift in Sources */, C4F8B18E298AC288005C86A5 /* AbstractTimer+CoreDataProperties.swift in Sources */, C4A16DC829D311C800143D5E /* Extensions.swift in Sources */, C4F8B1AE298AC451005C86A5 /* Alarm+CoreDataProperties.swift in Sources */, + C4C8266A2B0E41D20036C666 /* TimeRange+CoreDataProperties.swift in Sources */, C4BA2B32299F75DE00CB4FBA /* DefaultView.swift in Sources */, C48ECC0829DAC45900DE5A66 /* AppGuard.swift in Sources */, C438C8182982BFC100BF3EF9 /* Persistence.swift in Sources */, @@ -1474,19 +1477,18 @@ C4BA2B38299F82FF00CB4FBA /* Fakes.swift in Sources */, C438C7F529812BB200BF3EF9 /* IntentHandler.swift in Sources */, C473C33D29ACEC4F0056B38A /* Tip.swift in Sources */, + C4C8266B2B0E41D20036C666 /* TimeRange+CoreDataProperties.swift in Sources */, C4F8B19C298AC288005C86A5 /* AbstractTimer+CoreDataProperties.swift in Sources */, C4556F7429E40EC500DEB40B /* Codable+Extensions.swift in Sources */, - C4BA2B1C299BE6A100CB4FBA /* IntervalGroup+CoreDataClass.swift in Sources */, - C4BA2B1F299BE6A100CB4FBA /* Countdown+CoreDataProperties.swift in Sources */, C4F8B1A3298AC288005C86A5 /* Activity+CoreDataClass.swift in Sources */, C4F8B19A298AC288005C86A5 /* Alarm+CoreDataClass.swift in Sources */, C438C80529813FB400BF3EF9 /* TimeInterval+Extensions.swift in Sources */, C473C2F629A8DB1D0056B38A /* Sound.swift in Sources */, - C4BA2B1B299BE6A100CB4FBA /* Interval+CoreDataProperties.swift in Sources */, C438C802298132B900BF3EF9 /* LeCountdown.xcdatamodeld in Sources */, C4F8B1A0298AC288005C86A5 /* Activity+CoreDataProperties.swift in Sources */, C42E96FE29E5B5CD005B1B8C /* Filter.swift in Sources */, C438C81A2982BFF100BF3EF9 /* NSManagedContext+Extensions.swift in Sources */, + C4C8266F2B0E41DB0036C666 /* TimeRange+CoreDataClass.swift in Sources */, C4F8B19D298AC288005C86A5 /* AbstractTimer+CoreDataClass.swift in Sources */, C4BA2B3C299F838000CB4FBA /* Model+SharedExtensions.swift in Sources */, C4286EB22A1B75C60070D075 /* BoringContext.swift in Sources */, @@ -1496,14 +1498,13 @@ C438C8012981327600BF3EF9 /* Persistence.swift in Sources */, C473C31A29A926F50056B38A /* LaunchWidget.intentdefinition in Sources */, C4BA2B5D299FFAB000CB4FBA /* Logger.swift in Sources */, + C4C8266E2B0E41D20036C666 /* Countdown+CoreDataProperties.swift in Sources */, C48ECC0929DAC47200DE5A66 /* AppGuard.swift in Sources */, C473C2FB29A8DC3A0056B38A /* LaunchWidgetAttributes.swift in Sources */, C4556F7329E40EC200DEB40B /* FileUtils.swift in Sources */, - C4BA2B4B299FCE0C00CB4FBA /* IntervalGroup+CoreDataProperties.swift in Sources */, C4556F7229E40EBF00DEB40B /* FileLogger.swift in Sources */, C4F8B1B1298AC451005C86A5 /* AbstractSoundTimer+CoreDataClass.swift in Sources */, C473C2F129A8DA0B0056B38A /* Conductor.swift in Sources */, - C4BA2B1E299BE6A100CB4FBA /* Interval+CoreDataClass.swift in Sources */, C473C2FC29A8DC4B0056B38A /* Date+Extensions.swift in Sources */, C4286EA32A1503320070D075 /* Stopwatch+CoreDataClass.swift in Sources */, C473C2F429A8DAE70056B38A /* Model+Extensions.swift in Sources */, @@ -2054,6 +2055,7 @@ C4060DCA297AE73D003FAB80 /* LeCountdown.xcdatamodeld */ = { isa = XCVersionGroup; children = ( + C4C8265A2B0E40350036C666 /* LeCountdown.0.6.6.xcdatamodel */, C454892C2A28D9610047D39E /* LeCountdown.0.6.5.xcdatamodel */, C4A16DCA29D323CF00143D5E /* LeCountdown.0.6.4.xcdatamodel */, C4A16DBD29D1C9DE00143D5E /* LeCountdown.0.6.3.xcdatamodel */, @@ -2068,7 +2070,7 @@ C418A14F298428CB00C22230 /* LeCountdown.0.1.xcdatamodel */, C4060DCB297AE73D003FAB80 /* LeCountdown.xcdatamodel */, ); - currentVersion = C454892C2A28D9610047D39E /* LeCountdown.0.6.5.xcdatamodel */; + currentVersion = C4C8265A2B0E40350036C666 /* LeCountdown.0.6.6.xcdatamodel */; path = LeCountdown.xcdatamodeld; sourceTree = ""; versionGroupType = wrapper.xcdatamodel; diff --git a/LeCountdown/Model/Generation/Countdown+CoreDataProperties.swift b/LeCountdown/Model/Generation/Countdown+CoreDataProperties.swift index 79d4b93..7391988 100644 --- a/LeCountdown/Model/Generation/Countdown+CoreDataProperties.swift +++ b/LeCountdown/Model/Generation/Countdown+CoreDataProperties.swift @@ -2,7 +2,7 @@ // Countdown+CoreDataProperties.swift // LeCountdown // -// Created by Laurent Morvillier on 14/02/2023. +// Created by Laurent Morvillier on 22/11/2023. // // @@ -17,6 +17,23 @@ extension Countdown { } @NSManaged public var duration: Double - @NSManaged public var group: IntervalGroup? + @NSManaged public var timeRanges: NSSet? + +} + +// MARK: Generated accessors for timeRanges +extension Countdown { + + @objc(addTimeRangesObject:) + @NSManaged public func addToTimeRanges(_ value: TimeRange) + + @objc(removeTimeRangesObject:) + @NSManaged public func removeFromTimeRanges(_ value: TimeRange) + + @objc(addTimeRanges:) + @NSManaged public func addToTimeRanges(_ values: NSSet) + + @objc(removeTimeRanges:) + @NSManaged public func removeFromTimeRanges(_ values: NSSet) } diff --git a/LeCountdown/Model/Generation/Interval+CoreDataClass.swift b/LeCountdown/Model/Generation/Interval+CoreDataClass.swift deleted file mode 100644 index 31550c7..0000000 --- a/LeCountdown/Model/Generation/Interval+CoreDataClass.swift +++ /dev/null @@ -1,15 +0,0 @@ -// -// Interval+CoreDataClass.swift -// LeCountdown -// -// Created by Laurent Morvillier on 14/02/2023. -// -// - -import Foundation -import CoreData - -@objc(Interval) -public class Interval: NSManagedObject { - -} diff --git a/LeCountdown/Model/Generation/Interval+CoreDataProperties.swift b/LeCountdown/Model/Generation/Interval+CoreDataProperties.swift deleted file mode 100644 index a2d4520..0000000 --- a/LeCountdown/Model/Generation/Interval+CoreDataProperties.swift +++ /dev/null @@ -1,27 +0,0 @@ -// -// Interval+CoreDataProperties.swift -// LeCountdown -// -// Created by Laurent Morvillier on 14/02/2023. -// -// - -import Foundation -import CoreData - - -extension Interval { - - @nonobjc public class func fetchRequest() -> NSFetchRequest { - return NSFetchRequest(entityName: "Interval") - } - - @NSManaged public var duration: Double - @NSManaged public var soundList: String? - @NSManaged public var group: IntervalGroup? - -} - -extension Interval : Identifiable { - -} diff --git a/LeCountdown/Model/Generation/IntervalGroup+CoreDataClass.swift b/LeCountdown/Model/Generation/IntervalGroup+CoreDataClass.swift deleted file mode 100644 index 3c3ba48..0000000 --- a/LeCountdown/Model/Generation/IntervalGroup+CoreDataClass.swift +++ /dev/null @@ -1,15 +0,0 @@ -// -// IntervalGroup+CoreDataClass.swift -// LeCountdown -// -// Created by Laurent Morvillier on 14/02/2023. -// -// - -import Foundation -import CoreData - -@objc(IntervalGroup) -public class IntervalGroup: NSManagedObject { - -} diff --git a/LeCountdown/Model/Generation/IntervalGroup+CoreDataProperties.swift b/LeCountdown/Model/Generation/IntervalGroup+CoreDataProperties.swift deleted file mode 100644 index 8300975..0000000 --- a/LeCountdown/Model/Generation/IntervalGroup+CoreDataProperties.swift +++ /dev/null @@ -1,44 +0,0 @@ -// -// IntervalGroup+CoreDataProperties.swift -// LeCountdown -// -// Created by Laurent Morvillier on 17/02/2023. -// -// - -import Foundation -import CoreData - - -extension IntervalGroup { - - @nonobjc public class func fetchRequest() -> NSFetchRequest { - return NSFetchRequest(entityName: "IntervalGroup") - } - - @NSManaged public var repeatCount: Int16 - @NSManaged public var countdown: Countdown? - @NSManaged public var intervals: NSSet? - -} - -// MARK: Generated accessors for intervals -extension IntervalGroup { - - @objc(addIntervalsObject:) - @NSManaged public func addToIntervals(_ value: Interval) - - @objc(removeIntervalsObject:) - @NSManaged public func removeFromIntervals(_ value: Interval) - - @objc(addIntervals:) - @NSManaged public func addToIntervals(_ values: NSSet) - - @objc(removeIntervals:) - @NSManaged public func removeFromIntervals(_ values: NSSet) - -} - -extension IntervalGroup : Identifiable { - -} diff --git a/LeCountdown/Model/Generation/TimeRange+CoreDataClass.swift b/LeCountdown/Model/Generation/TimeRange+CoreDataClass.swift new file mode 100644 index 0000000..25cd2cb --- /dev/null +++ b/LeCountdown/Model/Generation/TimeRange+CoreDataClass.swift @@ -0,0 +1,15 @@ +// +// TimeRange+CoreDataClass.swift +// LeCountdown +// +// Created by Laurent Morvillier on 22/11/2023. +// +// + +import Foundation +import CoreData + +@objc(TimeRange) +public class TimeRange: NSManagedObject { + +} diff --git a/LeCountdown/Model/Generation/TimeRange+CoreDataProperties.swift b/LeCountdown/Model/Generation/TimeRange+CoreDataProperties.swift new file mode 100644 index 0000000..12e59a5 --- /dev/null +++ b/LeCountdown/Model/Generation/TimeRange+CoreDataProperties.swift @@ -0,0 +1,29 @@ +// +// TimeRange+CoreDataProperties.swift +// LeCountdown +// +// Created by Laurent Morvillier on 22/11/2023. +// +// + +import Foundation +import CoreData + + +extension TimeRange { + + @nonobjc public class func fetchRequest() -> NSFetchRequest { + return NSFetchRequest(entityName: "TimeRange") + } + + @NSManaged public var duration: Double + @NSManaged public var soundList: String? + @NSManaged public var name: String? + @NSManaged public var order: Int16 + @NSManaged public var countdown: Countdown? + +} + +extension TimeRange : Identifiable { + +} diff --git a/LeCountdown/Model/LeCountdown.xcdatamodeld/.xccurrentversion b/LeCountdown/Model/LeCountdown.xcdatamodeld/.xccurrentversion index 15b7600..35eb718 100644 --- a/LeCountdown/Model/LeCountdown.xcdatamodeld/.xccurrentversion +++ b/LeCountdown/Model/LeCountdown.xcdatamodeld/.xccurrentversion @@ -3,6 +3,6 @@ _XCCurrentVersionName - LeCountdown.0.6.5.xcdatamodel + LeCountdown.0.6.6.xcdatamodel diff --git a/LeCountdown/Model/LeCountdown.xcdatamodeld/LeCountdown.0.6.6.xcdatamodel/contents b/LeCountdown/Model/LeCountdown.xcdatamodeld/LeCountdown.0.6.6.xcdatamodel/contents new file mode 100644 index 0000000..5eb7ab0 --- /dev/null +++ b/LeCountdown/Model/LeCountdown.xcdatamodeld/LeCountdown.0.6.6.xcdatamodel/contents @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/LeCountdown/Model/Model+Extensions.swift b/LeCountdown/Model/Model+Extensions.swift index b0f05f6..1c814fd 100644 --- a/LeCountdown/Model/Model+Extensions.swift +++ b/LeCountdown/Model/Model+Extensions.swift @@ -158,3 +158,13 @@ extension CustomSound : Localized { } +extension TimeRange { + + static func fake(context: NSManagedObjectContext) -> TimeRange { + let timeRange = TimeRange(context: context) + timeRange.duration = 30.0 + timeRange.name = "Pause" + return timeRange + } + +} diff --git a/LeCountdown/Model/Model+SharedExtensions.swift b/LeCountdown/Model/Model+SharedExtensions.swift index 26f16ac..fee7f19 100644 --- a/LeCountdown/Model/Model+SharedExtensions.swift +++ b/LeCountdown/Model/Model+SharedExtensions.swift @@ -49,6 +49,10 @@ extension Countdown { override var defaultName: String { return NSLocalizedString("Countdown", comment: "") } + + var rangeCount: Int { + return self.timeRanges?.count ?? 0 + } } diff --git a/LeCountdown/Views/Countdown/CountdownFormView.swift b/LeCountdown/Views/Countdown/CountdownFormView.swift index 841d516..34ea336 100644 --- a/LeCountdown/Views/Countdown/CountdownFormView.swift +++ b/LeCountdown/Views/Countdown/CountdownFormView.swift @@ -16,6 +16,8 @@ enum CountdownField: Int, Hashable { struct CountdownFormView : View { + @Environment(\.managedObjectContext) private var viewContext + @FocusState var focusedField: CountdownField? @EnvironmentObject var model: TimerModel @@ -25,25 +27,66 @@ struct CountdownFormView : View { var imageBinding: Binding var repeatCountBinding: Binding - + var hasRanges: Bool + var intervalRepeatBinding: Binding? = nil + @State var showRangeSheet = false + + @State var selectedRange: TimeRange? = nil + var body: some View { Form { - Section(header: Text("Name for tracking the activity")) { - TextField("name", text: nameBinding) - .focused($focusedField, equals: .name) - .submitLabel(.continue) - .onSubmit { - self.focusedField = nil + if self.hasRanges { + + Section(header: Text("Name for tracking the activity")) { + TextField("name", text: nameBinding) + .focused($focusedField, equals: .name) + .submitLabel(.continue) + .onSubmit { + self.focusedField = nil + } + } + + Section { + ForEach(self.model.ranges) { range in + Button { + self.selectedRange = range + } label: { + LabeledContent(range.name ?? "", value: range.duration.hourMinuteSecond) + } } - } + } + + Section { + Button { + self._addInterval() + } label: { + HStack { + Image(systemName: "plus.circle") + Text("Add range") + } + } + } + + } else { + + Section(header: Text("Name for tracking the activity")) { + TextField("name", text: nameBinding) + .focused($focusedField, equals: .name) + .submitLabel(.continue) + .onSubmit { + self.focusedField = nil + } + } + + Section { + TimePickerView(duration: self.durationBinding) + } header: { + LabeledContent("Duration", value: self.duration().hourMinuteSecond).font(.footnote) + } - Section { - TimePickerView(duration: self.durationBinding) - } header: { - LabeledContent("Duration", value: self.duration().hourMinuteSecond).font(.footnote) } SoundFormView( @@ -51,9 +94,16 @@ struct CountdownFormView : View { imageBinding: self.imageBinding, repeatCountBinding: self.repeatCountBinding) } + .sheet(item: self.$selectedRange) { item in + RangeFormView(timeRange: item, selectedItem: self.$selectedRange) + } } + fileprivate func _addInterval() { + self.selectedRange = self.model.addInterval(context: self.viewContext) + } + func duration() -> TimeInterval { return self.durationBinding.wrappedValue } @@ -62,14 +112,13 @@ struct CountdownFormView : View { struct CountdownFormView_Previews: PreviewProvider { - @FocusState static var textFieldIsFocused: Bool - static var previews: some View { CountdownFormView( nameBinding: .constant(""), durationBinding: .constant(0.0), imageBinding: .constant(.pic3), repeatCountBinding: .constant(2), + hasRanges: true, intervalRepeatBinding: .constant(2)) .environmentObject(TimerModel()) } diff --git a/LeCountdown/Views/Countdown/NewCountdownView.swift b/LeCountdown/Views/Countdown/NewCountdownView.swift index ca7d49d..391f0bd 100644 --- a/LeCountdown/Views/Countdown/NewCountdownView.swift +++ b/LeCountdown/Views/Countdown/NewCountdownView.swift @@ -15,16 +15,18 @@ struct NewCountdownView : View { @Environment(\.managedObjectContext) private var viewContext @Binding var isPresented: Bool + var hasRanges: Bool var userActivity: NSUserActivity - init(isPresented: Binding) { + init(isPresented: Binding, hasRanges: Bool) { _isPresented = isPresented + self.hasRanges = hasRanges self.userActivity = Shortcut.newCountdown.userActivity } var body: some View { NavigationStack { - CountdownEditView(isPresented: $isPresented) + CountdownEditView(isPresented: $isPresented, hasRanges: self.hasRanges) .environment(\.managedObjectContext, viewContext) .onAppear { self.userActivity.becomeCurrent() @@ -43,22 +45,23 @@ struct CountdownEditView : View { @Environment(\.dismiss) private var dismiss @StateObject var model: TimerModel = TimerModel() - + var countdown: Countdown? = nil var preset: Preset? = nil - + @Binding var isPresented: Bool - + var hasRanges: Bool + @State var nameString: String = "" @State var duration: TimeInterval = 0.0 - + @State var soundRepeatCount: Int16 = 0 @State var image: CoolPic = .pic1 - + @State var deleteConfirmationShown: Bool = false @State var activityNameConfirmationShown: Bool = false @State fileprivate var _rename: Bool? = nil - + @State var errorShown: Bool = false @State var error: Error? = nil @@ -66,17 +69,24 @@ struct CountdownEditView : View { @State var _hasLoaded = false @Environment(\.isPresented) var envIsPresented - + @FocusState private var focusedField: CountdownField? - - init(isPresented: Binding, countdown: Countdown? = nil) { + + init(isPresented: Binding, hasRanges: Bool) { + _isPresented = isPresented + self.hasRanges = hasRanges + } + + init(isPresented: Binding, countdown: Countdown) { _isPresented = isPresented self.countdown = countdown + self.hasRanges = countdown.rangeCount > 1 } init(isPresented: Binding, preset: Preset) { _isPresented = isPresented self.preset = preset + self.hasRanges = preset.intervalGroup.intervals.count > 1 } var body: some View { @@ -97,7 +107,8 @@ struct CountdownEditView : View { nameBinding: $nameString, durationBinding: $duration, imageBinding: $image, - repeatCountBinding: $soundRepeatCount) + repeatCountBinding: $soundRepeatCount, + hasRanges: self.hasRanges) .environmentObject(self.model) .toolbar { @@ -186,7 +197,9 @@ struct CountdownEditView : View { fileprivate func _loadPreset(_ preset: Preset) { self.nameString = preset.localizedName self.duration = preset.duration - self.model.group = preset.intervalGroup + + self.model.ranges = preset.ranges(context: self.viewContext) + self.model.soundModel.loadPreset(preset) } @@ -245,28 +258,22 @@ struct CountdownEditView : View { cd.playableIds = self.model.soundModel.playableIds cd.setConfirmationSounds(self.model.confirmationSoundModel.sounds) cd.repeatCount = self.soundRepeatCount + + if self.model.ranges.count > 0 { + + if let timeRanges = cd.timeRanges { + cd.removeFromTimeRanges(timeRanges) + } + + for (index, range) in self.model.ranges.enumerated() { + range.order = Int16(index) + cd.addToTimeRanges(range) + } + } if !self.nameString.isEmpty { - let trimmed = self.nameString.trimmingCharacters(in: .whitespacesAndNewlines) cd.activity = CoreDataRequests.getOrCreateActivity(name: trimmed) - -// if let activity = cd.activity, let currentActivityName = activity.name, trimmed != currentActivityName { -// -// switch self._rename { -// case .none: -// self.activityNameConfirmationShown = true -// return -// case .some(let rename): -// if rename { -// activity.name = trimmed -// } else { -// cd.activity = CoreDataRequests.getOrCreateActivity(name: trimmed) -// } -// } -// } else { -// cd.activity = CoreDataRequests.getOrCreateActivity(name: trimmed) -// } } self._saveContext() @@ -312,7 +319,11 @@ struct CountdownEditView : View { struct NewCountdownView_Previews: PreviewProvider { static var previews: some View { - NewCountdownView(isPresented: .constant(true)) + NewCountdownView(isPresented: .constant(true), hasRanges: false) .environment(\.managedObjectContext, PersistenceController.preview.container.viewContext) + +// NewCountdownView(isPresented: .constant(true), hasRanges: true) +// .environment(\.managedObjectContext, PersistenceController.preview.container.viewContext) + } } diff --git a/LeCountdown/Views/Countdown/RangeFormView.swift b/LeCountdown/Views/Countdown/RangeFormView.swift new file mode 100644 index 0000000..3e3eaf4 --- /dev/null +++ b/LeCountdown/Views/Countdown/RangeFormView.swift @@ -0,0 +1,67 @@ +// +// RangeFormView.swift +// LeCountdown +// +// Created by Laurent Morvillier on 22/11/2023. +// + +import SwiftUI + +struct RangeFormView: View { + + var timeRange: TimeRange + + @Binding var selectedItem: TimeRange? + + @State var name: String = "" + @State var duration: TimeInterval = 0.0 + + var body: some View { + + Form { + Section(header: Text("Name")) { + TextField("name", text: self.$name) + } + + Section { + TimePickerView(duration: self.$duration) + } header: { + LabeledContent("Duration", value: self.duration.hourMinuteSecond) + .font(.footnote) + } + + Section { + Button { + self.timeRange.name = self.name + self.timeRange.duration = self.duration + self.selectedItem = nil + } label: { + HStack { + Spacer() + Text("Done").fontWeight(.bold) + Spacer() + } + } + } + }.onAppear { + if let name = self.timeRange.name { + self.name = name + } + self.duration = self.timeRange.duration + } + } + +} + +struct RangeFormView_Previews: PreviewProvider { + + static var previews: some View { + Text("tilting crash") + +// RangeFormView( +// timeRange: TimeRange.fake(context: PersistenceController.preview.container.viewContext), +// isPresented: .constant(true)) +// .environment(\.managedObjectContext, PersistenceController.preview.container.viewContext) + + } +} diff --git a/LeCountdown/Views/NewDataView.swift b/LeCountdown/Views/NewDataView.swift index a0d3b9d..fa4b379 100644 --- a/LeCountdown/Views/NewDataView.swift +++ b/LeCountdown/Views/NewDataView.swift @@ -23,45 +23,45 @@ enum DataTab: Int, Identifiable, CaseIterable { } -struct NewDataView: View { - - @Environment(\.managedObjectContext) private var viewContext - - @Binding var isPresented: Bool - - @State var selection: Int = 0 - - var body: some View { - - NavigationStack { - - VStack { - - Picker("", selection: $selection) { - ForEach(DataTab.allCases) { tab in - Text(tab.localizedString) - } - } - .pickerStyle(.segmented) - .padding(.horizontal) - - TabView(selection: $selection) { - NewCountdownView(isPresented: $isPresented) - .tag(0) - .environment(\.managedObjectContext, viewContext) - NewStopwatchView(isPresented: $isPresented) - .tag(1) - .environment(\.managedObjectContext, viewContext) - }.tabViewStyle(.page(indexDisplayMode: .never)) - } - } - - } -} +//struct NewDataView: View { +// +// @Environment(\.managedObjectContext) private var viewContext +// +// @Binding var isPresented: Bool +// +// @State var selection: Int = 0 +// +// var body: some View { +// +// NavigationStack { +// +// VStack { +// +// Picker("", selection: $selection) { +// ForEach(DataTab.allCases) { tab in +// Text(tab.localizedString) +// } +// } +// .pickerStyle(.segmented) +// .padding(.horizontal) +// +// TabView(selection: $selection) { +// NewCountdownView(isPresented: $isPresented) +// .tag(0) +// .environment(\.managedObjectContext, viewContext) +// NewStopwatchView(isPresented: $isPresented) +// .tag(1) +// .environment(\.managedObjectContext, viewContext) +// }.tabViewStyle(.page(indexDisplayMode: .never)) +// } +// } +// +// } +//} -struct NewDataView_Previews: PreviewProvider { - static var previews: some View { - NewDataView(isPresented: .constant(true)) - .environment(\.managedObjectContext, PersistenceController.preview.container.viewContext) - } -} +//struct NewDataView_Previews: PreviewProvider { +// static var previews: some View { +// NewDataView(isPresented: .constant(true)) +// .environment(\.managedObjectContext, PersistenceController.preview.container.viewContext) +// } +//} diff --git a/LeCountdown/Views/PresetsView.swift b/LeCountdown/Views/PresetsView.swift index 51ac80c..a736f85 100644 --- a/LeCountdown/Views/PresetsView.swift +++ b/LeCountdown/Views/PresetsView.swift @@ -6,6 +6,7 @@ // import SwiftUI +import CoreData class PresetModel : ObservableObject { @@ -137,7 +138,7 @@ struct PresetsView: View { .environment(\.managedObjectContext, viewContext) }) .sheet(isPresented: $isShowingNewCountdown, content: { - NewCountdownView(isPresented: $isShowingNewCountdown) + NewCountdownView(isPresented: $isShowingNewCountdown, hasRanges: false) .environment(\.managedObjectContext, viewContext) }) .sheet(isPresented: $isPresented, content: { @@ -235,6 +236,7 @@ struct CountdownIntervalGroup { } struct CountdownInterval { + var name: String? var duration: TimeInterval var sound: Sound? } @@ -246,7 +248,7 @@ enum Preset: Int, Identifiable, CaseIterable { case meditation case nap case workout -// case runningSplits + case runningSplits case pasta case rice case blackTea @@ -268,7 +270,7 @@ enum Preset: Int, Identifiable, CaseIterable { case .mediumBoiledEggs: return NSLocalizedString("Medium boiled eggs", comment: "") case .meditation: return NSLocalizedString("Meditation", comment: "") case .nap: return NSLocalizedString("Nap", comment: "") -// case .runningSplits: return NSLocalizedString("Running splits", comment: "") + case .runningSplits: return NSLocalizedString("Running splits", comment: "") case .toothbrushing: return NSLocalizedString("Tooth brushing", comment: "") case .blackTea: return NSLocalizedString("Black tea", comment: "") case .greenTea: return NSLocalizedString("Green tea", comment: "") @@ -284,14 +286,19 @@ enum Preset: Int, Identifiable, CaseIterable { } var intervalGroup: CountdownIntervalGroup { -// switch self { -// case .runningSplits: -// let runInterval = CountdownInterval(duration: 30.0, sound: Sound.sbArpeggio_Loop_River) -// let breakInterval = CountdownInterval(duration: 30.0, sound: Sound.sbLoop_ToneSD_Boavista) -// return CountdownIntervalGroup(repeatCount: 8, intervals: [runInterval, breakInterval]) -// default: - return CountdownIntervalGroup(repeatCount: 0, intervals: [CountdownInterval(duration: self.duration)]) -// } + return CountdownIntervalGroup(repeatCount: 0, intervals: [CountdownInterval(duration: self.duration)]) + } + + func ranges(context: NSManagedObjectContext) -> [TimeRange] { + switch self { + case .runningSplits: + return [] + default: + let timeRange = TimeRange(context: context) + timeRange.name = self.localizedName + timeRange.duration = self.duration + return [timeRange] + } } var duration: TimeInterval { @@ -301,7 +308,7 @@ enum Preset: Int, Identifiable, CaseIterable { case .hardBoiledEggs: return 10 * 60 case .meditation: return 15 * 60 case .nap: return 20 * 60 -// case .runningSplits: return 0.0 + case .runningSplits: return 0.0 case .toothbrushing: return 2 * 60.0 case .greenTea: return 3 * 60.0 case .blackTea: return 4 * 60.0 @@ -318,7 +325,7 @@ enum Preset: Int, Identifiable, CaseIterable { var playlist: Playlist { switch self { - case .softBoiled, .mediumBoiledEggs, .hardBoiledEggs, .pasta, .rice, .toothbrushing, .workout, .stretching, .work: + case .softBoiled, .mediumBoiledEggs, .hardBoiledEggs, .pasta, .rice, .toothbrushing, .workout, .stretching, .work, .runningSplits: return .stephanBodzin case .meditation, .blackTea, .greenTea, .writing, .reading: return .relax @@ -327,11 +334,8 @@ enum Preset: Int, Identifiable, CaseIterable { } } -// var sounds: Set { -// return Set(SoundCatalog.main.sounds(for: self.playlist)) -// } - var formattedDuration: String { + let group = self.intervalGroup let count = group.repeatCount.formatted() let durations = group.intervals.map { $0.duration.hourMinuteSecond } diff --git a/LeCountdown/Views/Reusable/TimerModel.swift b/LeCountdown/Views/Reusable/TimerModel.swift index a4bb1b2..0970171 100644 --- a/LeCountdown/Views/Reusable/TimerModel.swift +++ b/LeCountdown/Views/Reusable/TimerModel.swift @@ -8,6 +8,7 @@ import Foundation import SwiftUI import Combine +import CoreData protocol SoundHolder { func selectSound(_ sound: Sound, selected: Bool) @@ -19,9 +20,19 @@ class TimerModel: ObservableObject { @Published var soundModel: SoundModel = SoundModel() @Published var confirmationSoundModel: SoundModel = SoundModel() - @Published var group: CountdownIntervalGroup = - CountdownIntervalGroup(repeatCount: 0, intervals: []) +// @Published var group: CountdownIntervalGroup = +// CountdownIntervalGroup(repeatCount: 0, intervals: []) + @Published var ranges: [TimeRange] = [] + + func addInterval(context: NSManagedObjectContext) -> TimeRange { + let timeRange = TimeRange(context: context) + self.ranges.append(timeRange) + return timeRange +// self.editedRange = timeRange + } + +// @Published var editedRange: TimeRange? = nil } class SoundModel: ObservableObject, SoundHolder { diff --git a/LeCountdown/Views/StartView.swift b/LeCountdown/Views/StartView.swift index 139c89c..64300c7 100644 --- a/LeCountdown/Views/StartView.swift +++ b/LeCountdown/Views/StartView.swift @@ -15,6 +15,7 @@ struct StartView: View { @Binding var isPresented: Bool @State var showTimerScreen: Bool = false + @State var showMultiTimerScreen: Bool = false @State var showStopwatchScreen: Bool = false var body: some View { @@ -29,61 +30,52 @@ struct StartView: View { PresetSelectionView(model: self.model).monospaced() - HStack(spacing: 4.0) { + VStack(spacing: 4.0) { + Button { self.showTimerScreen = true } label: { - HStack { - Image(systemName: "timer")//.font(.title) - Text("Create your own timer") - } - .multilineTextAlignment(.leading) - .padding() - .frame(maxWidth: .infinity, maxHeight: .infinity) - .foregroundColor(.white) - .background(Color.accentColor) - .cornerRadius(12.0) + ImageButton(stringKey: "Create your own timer", systemImage: "timer") }.sheet(isPresented: self.$showTimerScreen) { - NewCountdownView(isPresented: $showTimerScreen) + NewCountdownView(isPresented: $showTimerScreen, hasRanges: false) + .environment(\.managedObjectContext, viewContext) + } + + Button { + self.showMultiTimerScreen = true + } label: { + ImageButton(stringKey: "Create a timer with phases", systemImage: "plus.square.on.square") + } + .sheet(isPresented: self.$showMultiTimerScreen) { + NewCountdownView(isPresented: $showMultiTimerScreen, hasRanges: true) .environment(\.managedObjectContext, viewContext) } Button { self.showStopwatchScreen = true } label: { - HStack { - Image(systemName: "stopwatch")//.font(.title) - Text("Create your own stopwatch") - } - .multilineTextAlignment(.leading) - .padding() - .frame(maxWidth: .infinity, maxHeight: .infinity) - .foregroundColor(.white) - .background(Color.accentColor) - .cornerRadius(12.0) + ImageButton(stringKey: "Create your own stopwatch", systemImage: "stopwatch") }.sheet(isPresented: self.$showStopwatchScreen) { NewStopwatchView(isPresented: $showStopwatchScreen) .environment(\.managedObjectContext, viewContext) } + + Button { + self._done() + } label: { + Text("Done") + .font(.title2).fontWeight(.semibold) + .padding() + .frame(maxWidth: .infinity) + .foregroundColor(.white) + .background(Color.accentColor) + .cornerRadius(12.0) +// .padding(.horizontal, 4.0) + + } } - .font(.footnote) - .frame(height: 80.0) +// .font(.footnote) .padding(4.0) - - Button { - self._done() - } label: { - Text("Done") - .font(.title2).fontWeight(.semibold) - .padding() - .frame(maxWidth: .infinity) - .foregroundColor(.white) - .background(Color.accentColor) - .cornerRadius(12.0) - .padding(.horizontal, 4.0) - - } - } } @@ -108,6 +100,25 @@ struct StartView: View { } +struct ImageButton: View { + + var stringKey: LocalizedStringKey + var systemImage: String + + var body: some View { + HStack { + Image(systemName: self.systemImage) + Text(self.stringKey) + Spacer() + } + .padding() + .foregroundColor(.white) + .background(Color.accentColor) + .cornerRadius(12.0) + } + +} + class Customization: ObservableObject { var preset: Preset diff --git a/TimeRange+CoreDataClass.swift b/TimeRange+CoreDataClass.swift new file mode 100644 index 0000000..facd656 --- /dev/null +++ b/TimeRange+CoreDataClass.swift @@ -0,0 +1,15 @@ +// +// TimeRange+CoreDataClass.swift +// LeCountdown +// +// Created by Laurent Morvillier on 23/11/2023. +// +// + +import Foundation +import CoreData + +@objc(TimeRange) +public class TimeRange: NSManagedObject { + +} diff --git a/TimeRange+CoreDataProperties.swift b/TimeRange+CoreDataProperties.swift new file mode 100644 index 0000000..26c41bd --- /dev/null +++ b/TimeRange+CoreDataProperties.swift @@ -0,0 +1,29 @@ +// +// TimeRange+CoreDataProperties.swift +// LeCountdown +// +// Created by Laurent Morvillier on 23/11/2023. +// +// + +import Foundation +import CoreData + + +extension TimeRange { + + @nonobjc public class func fetchRequest() -> NSFetchRequest { + return NSFetchRequest(entityName: "TimeRange") + } + + @NSManaged public var duration: Double + @NSManaged public var name: String? + @NSManaged public var order: Int16 + @NSManaged public var soundList: String? + @NSManaged public var countdown: Countdown? + +} + +extension TimeRange : Identifiable { + +}