adds interval UI and db changes

splits
Laurent 2 years ago
parent b351b7f394
commit b3447d2f00
  1. 84
      LeCountdown.xcodeproj/project.pbxproj
  2. 21
      LeCountdown/Model/Generation/Countdown+CoreDataProperties.swift
  3. 15
      LeCountdown/Model/Generation/Interval+CoreDataClass.swift
  4. 27
      LeCountdown/Model/Generation/Interval+CoreDataProperties.swift
  5. 15
      LeCountdown/Model/Generation/IntervalGroup+CoreDataClass.swift
  6. 44
      LeCountdown/Model/Generation/IntervalGroup+CoreDataProperties.swift
  7. 15
      LeCountdown/Model/Generation/TimeRange+CoreDataClass.swift
  8. 29
      LeCountdown/Model/Generation/TimeRange+CoreDataProperties.swift
  9. 2
      LeCountdown/Model/LeCountdown.xcdatamodeld/.xccurrentversion
  10. 50
      LeCountdown/Model/LeCountdown.xcdatamodeld/LeCountdown.0.6.6.xcdatamodel/contents
  11. 10
      LeCountdown/Model/Model+Extensions.swift
  12. 4
      LeCountdown/Model/Model+SharedExtensions.swift
  13. 77
      LeCountdown/Views/Countdown/CountdownFormView.swift
  14. 75
      LeCountdown/Views/Countdown/NewCountdownView.swift
  15. 67
      LeCountdown/Views/Countdown/RangeFormView.swift
  16. 82
      LeCountdown/Views/NewDataView.swift
  17. 38
      LeCountdown/Views/PresetsView.swift
  18. 15
      LeCountdown/Views/Reusable/TimerModel.swift
  19. 89
      LeCountdown/Views/StartView.swift
  20. 15
      TimeRange+CoreDataClass.swift
  21. 29
      TimeRange+CoreDataProperties.swift

@ -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 = "<group>"; };
C47C933829F13BD100C780E2 /* AppleMusicPickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppleMusicPickerView.swift; sourceTree = "<group>"; };
C47C933C29F13DBD00C780E2 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = "<group>"; };
C48920662B0E57C900F6F4D8 /* RangeFormView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RangeFormView.swift; sourceTree = "<group>"; };
C48940DD2AC307860086F4FA /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = "<group>"; };
C498E59E298D4DEA00E90DE0 /* LiveTimerListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveTimerListView.swift; sourceTree = "<group>"; };
C498E5A0298D543900E90DE0 /* LiveTimer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveTimer.swift; sourceTree = "<group>"; };
@ -472,10 +468,6 @@
C4BA2B03299A42EF00CB4FBA /* NewDataView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewDataView.swift; sourceTree = "<group>"; };
C4BA2B05299A8F8D00CB4FBA /* PresetsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PresetsView.swift; sourceTree = "<group>"; };
C4BA2B07299BDAE000CB4FBA /* LeCountdown.0.6.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = LeCountdown.0.6.xcdatamodel; sourceTree = "<group>"; };
C4BA2B0A299BE61E00CB4FBA /* Interval+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Interval+CoreDataClass.swift"; sourceTree = "<group>"; };
C4BA2B0B299BE61E00CB4FBA /* Interval+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Interval+CoreDataProperties.swift"; sourceTree = "<group>"; };
C4BA2B0C299BE61E00CB4FBA /* IntervalGroup+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "IntervalGroup+CoreDataClass.swift"; sourceTree = "<group>"; };
C4BA2B0E299BE61E00CB4FBA /* Countdown+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Countdown+CoreDataProperties.swift"; sourceTree = "<group>"; };
C4BA2B24299D35C100CB4FBA /* HomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeView.swift; sourceTree = "<group>"; };
C4BA2B2C299E2DEE00CB4FBA /* Preferences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Preferences.swift; sourceTree = "<group>"; };
C4BA2B2E299E69A000CB4FBA /* View+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+Extension.swift"; sourceTree = "<group>"; };
@ -485,7 +477,6 @@
C4BA2B3D299FC86800CB4FBA /* StatisticsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatisticsView.swift; sourceTree = "<group>"; };
C4BA2B42299FCB2B00CB4FBA /* RecordsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecordsView.swift; sourceTree = "<group>"; };
C4BA2B46299FCD8B00CB4FBA /* LeCountdown.0.6.1.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = LeCountdown.0.6.1.xcdatamodel; sourceTree = "<group>"; };
C4BA2B47299FCE0C00CB4FBA /* IntervalGroup+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "IntervalGroup+CoreDataProperties.swift"; sourceTree = "<group>"; };
C4BA2B48299FCE0C00CB4FBA /* Stopwatch+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Stopwatch+CoreDataProperties.swift"; sourceTree = "<group>"; };
C4BA2B56299FFA4F00CB4FBA /* AppGuard.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppGuard.swift; sourceTree = "<group>"; };
C4BA2B5A299FFAB000CB4FBA /* Logger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = "<group>"; };
@ -499,6 +490,12 @@
C4BA2B7229A60CF000CB4FBA /* Shortcut.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Shortcut.swift; sourceTree = "<group>"; };
C4BA2B7829A65C1400CB4FBA /* UIDevice+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIDevice+Extensions.swift"; sourceTree = "<group>"; };
C4BCABB82A040B97009FFB0A /* QP01 0023 Surf moderate sandy.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = "QP01 0023 Surf moderate sandy.wav"; sourceTree = "<group>"; };
C4C8265A2B0E40350036C666 /* LeCountdown.0.6.6.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = LeCountdown.0.6.6.xcdatamodel; sourceTree = "<group>"; };
C4C8265D2B0E411A0036C666 /* TimeRange+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TimeRange+CoreDataClass.swift"; sourceTree = "<group>"; };
C4C826632B0E41610036C666 /* Countdown+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Countdown+CoreDataProperties.swift"; sourceTree = "<group>"; };
C4C826642B0E41610036C666 /* TimeRange+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TimeRange+CoreDataProperties.swift"; sourceTree = "<group>"; };
C4C826672B0E41D20036C666 /* TimeRange+CoreDataProperties.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "TimeRange+CoreDataProperties.swift"; sourceTree = "<group>"; };
C4C826682B0E41D20036C666 /* Countdown+CoreDataProperties.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Countdown+CoreDataProperties.swift"; sourceTree = "<group>"; };
C4E5D66429B73AED008E7465 /* StartTimerIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StartTimerIntent.swift; sourceTree = "<group>"; };
C4E5D66529B73AED008E7465 /* TimerIdentifierAppEntity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimerIdentifierAppEntity.swift; sourceTree = "<group>"; };
C4E5D66929B73FC6008E7465 /* TimerShortcuts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimerShortcuts.swift; sourceTree = "<group>"; };
@ -595,6 +592,7 @@
C438C7F329812BB200BF3EF9 /* LaunchIntents */,
C438C7CF2981216200BF3EF9 /* Frameworks */,
C4060DBD297AE73B003FAB80 /* Products */,
C48920632B0E422E00F6F4D8 /* Recovered References */,
);
sourceTree = "<group>";
};
@ -830,6 +828,15 @@
path = Sound_Assets;
sourceTree = "<group>";
};
C48920632B0E422E00F6F4D8 /* Recovered References */ = {
isa = PBXGroup;
children = (
C4C826632B0E41610036C666 /* Countdown+CoreDataProperties.swift */,
C4C826642B0E41610036C666 /* TimeRange+CoreDataProperties.swift */,
);
name = "Recovered References";
sourceTree = "<group>";
};
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 = "<group>";
@ -933,6 +938,7 @@
C4742B58298411E800D5D950 /* CountdownFormView.swift */,
C4F8B1B7298AC81D005C86A5 /* CountdownDialView.swift */,
C4060DF6297AFEF2003FAB80 /* NewCountdownView.swift */,
C48920662B0E57C900F6F4D8 /* RangeFormView.swift */,
);
path = Countdown;
sourceTree = "<group>";
@ -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 = "<group>";
versionGroupType = wrapper.xcdatamodel;

@ -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)
}

@ -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 {
}

@ -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<Interval> {
return NSFetchRequest<Interval>(entityName: "Interval")
}
@NSManaged public var duration: Double
@NSManaged public var soundList: String?
@NSManaged public var group: IntervalGroup?
}
extension Interval : Identifiable {
}

@ -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 {
}

@ -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<IntervalGroup> {
return NSFetchRequest<IntervalGroup>(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 {
}

@ -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 {
}

@ -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<TimeRange> {
return NSFetchRequest<TimeRange>(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 {
}

@ -3,6 +3,6 @@
<plist version="1.0">
<dict>
<key>_XCCurrentVersionName</key>
<string>LeCountdown.0.6.5.xcdatamodel</string>
<string>LeCountdown.0.6.6.xcdatamodel</string>
</dict>
</plist>

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="21754" systemVersion="22G91" minimumToolsVersion="Automatic" sourceLanguage="Swift" usedWithCloudKit="YES" userDefinedModelVersionIdentifier="">
<entity name="AbstractSoundTimer" representedClassName="AbstractSoundTimer" isAbstract="YES" parentEntity="AbstractTimer" elementID="soundList" syncable="YES">
<attribute name="confirmationSoundList" optional="YES" attributeType="String"/>
<attribute name="playableIds" optional="YES" attributeType="String"/>
<attribute name="repeatCount" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="YES"/>
</entity>
<entity name="AbstractTimer" representedClassName="AbstractTimer" isAbstract="YES" syncable="YES">
<attribute name="image" optional="YES" attributeType="String"/>
<attribute name="order" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="YES"/>
<relationship name="activity" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Activity" inverseName="timers" inverseEntity="Activity"/>
</entity>
<entity name="Activity" representedClassName="Activity" syncable="YES">
<attribute name="name" attributeType="String" defaultValueString=""/>
<relationship name="records" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="Record" inverseName="activity" inverseEntity="Record"/>
<relationship name="timers" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="AbstractTimer" inverseName="activity" inverseEntity="AbstractTimer"/>
</entity>
<entity name="Alarm" representedClassName="Alarm" parentEntity="AbstractSoundTimer" syncable="YES">
<attribute name="fireDate" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
</entity>
<entity name="Countdown" representedClassName="Countdown" parentEntity="AbstractSoundTimer" syncable="YES">
<attribute name="duration" attributeType="Double" defaultValueString="0" usesScalarValueType="YES"/>
<relationship name="timeRanges" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="TimeRange" inverseName="countdown" inverseEntity="TimeRange"/>
</entity>
<entity name="CustomSound" representedClassName="CustomSound" syncable="YES">
<attribute name="file" optional="YES" attributeType="String"/>
<attribute name="text" optional="YES" attributeType="String"/>
</entity>
<entity name="Record" representedClassName="Record" syncable="YES">
<attribute name="cancelled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="duration" attributeType="Double" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="end" attributeType="Date" defaultDateTimeInterval="696425400" usesScalarValueType="NO"/>
<attribute name="month" optional="YES" attributeType="Integer 16" usesScalarValueType="YES"/>
<attribute name="start" attributeType="Date" defaultDateTimeInterval="696425400" usesScalarValueType="NO"/>
<attribute name="year" optional="YES" attributeType="Integer 16" usesScalarValueType="YES"/>
<relationship name="activity" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Activity" inverseName="records" inverseEntity="Activity"/>
</entity>
<entity name="Stopwatch" representedClassName="Stopwatch" parentEntity="AbstractSoundTimer" syncable="YES">
<attribute name="end" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="sound" optional="YES" attributeType="Integer 16" usesScalarValueType="YES"/>
<attribute name="start" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
</entity>
<entity name="TimeRange" representedClassName="TimeRange" syncable="YES">
<attribute name="duration" attributeType="Double" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="name" optional="YES" attributeType="String"/>
<attribute name="order" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="soundList" optional="YES" attributeType="String"/>
<relationship name="countdown" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Countdown" inverseName="timeRanges" inverseEntity="Countdown"/>
</entity>
</model>

@ -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
}
}

@ -49,6 +49,10 @@ extension Countdown {
override var defaultName: String {
return NSLocalizedString("Countdown", comment: "")
}
var rangeCount: Int {
return self.timeRanges?.count ?? 0
}
}

@ -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<CoolPic>
var repeatCountBinding: Binding<Int16>
var hasRanges: Bool
var intervalRepeatBinding: Binding<Int>? = 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())
}

@ -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<Bool>) {
init(isPresented: Binding<Bool>, 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<Bool>, countdown: Countdown? = nil) {
init(isPresented: Binding<Bool>, hasRanges: Bool) {
_isPresented = isPresented
self.hasRanges = hasRanges
}
init(isPresented: Binding<Bool>, countdown: Countdown) {
_isPresented = isPresented
self.countdown = countdown
self.hasRanges = countdown.rangeCount > 1
}
init(isPresented: Binding<Bool>, 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)
}
}

@ -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)
}
}

@ -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)
// }
//}

@ -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<Sound> {
// 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 }

@ -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 {

@ -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

@ -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 {
}

@ -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<TimeRange> {
return NSFetchRequest<TimeRange>(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 {
}
Loading…
Cancel
Save