Ivan Valkou 5 лет назад
Родитель
Сommit
00dc7c4760
100 измененных файлов с 2413 добавлено и 56 удалено
  1. 575 15
      FreeAPS.xcodeproj/project.pbxproj
  2. 52 0
      FreeAPS.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
  3. 84 0
      FreeAPS.xcodeproj/xcuserdata/i.valkou.xcuserdatad/xcschemes/xcschememanagement.plist
  4. 0 24
      FreeAPS/ContentView.swift
  5. 0 17
      FreeAPS/FreeAPSApp.swift
  6. 0 0
      FreeAPS/Resources/Assets.xcassets/AccentColor.colorset/Contents.json
  7. 0 0
      FreeAPS/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json
  8. 0 0
      FreeAPS/Resources/Assets.xcassets/Contents.json
  9. 10 0
      FreeAPS/Resources/FreeAPS.entitlements
  10. 0 0
      FreeAPS/Resources/Info.plist
  11. 0 0
      FreeAPS/Resources/javascript/autosens-bundle.js
  12. 0 0
      FreeAPS/Resources/javascript/basal-set-temp-bundle.js
  13. 0 0
      FreeAPS/Resources/javascript/determine-basal-bundle.js
  14. 0 0
      FreeAPS/Resources/javascript/glucose-get-last-bundle.js
  15. 0 0
      FreeAPS/Resources/javascript/iob-bundle.js
  16. 0 0
      FreeAPS/Resources/javascript/meal-bundle.js
  17. 0 0
      FreeAPS/Resources/javascript/prepare-autosens.js
  18. 0 0
      FreeAPS/Resources/javascript/prepare-determine-basal.js
  19. 0 0
      FreeAPS/Resources/javascript/prepare-iob.js
  20. 0 0
      FreeAPS/Resources/javascript/prepare-meal.js
  21. 0 0
      FreeAPS/Resources/json/autosens.json
  22. 0 0
      FreeAPS/Resources/json/basal_profile.json
  23. 0 0
      FreeAPS/Resources/json/carbhistory.json
  24. 0 0
      FreeAPS/Resources/json/clock.json
  25. 0 0
      FreeAPS/Resources/json/entries.json
  26. 0 0
      FreeAPS/Resources/json/example.sh
  27. 0 0
      FreeAPS/Resources/json/glucose.json
  28. 0 0
      FreeAPS/Resources/json/iob.json
  29. 0 0
      FreeAPS/Resources/json/meal.json
  30. 0 0
      FreeAPS/Resources/json/profile.json
  31. 0 0
      FreeAPS/Resources/json/pumphistory.json
  32. 0 0
      FreeAPS/Resources/json/suggested.json
  33. 0 0
      FreeAPS/Resources/json/temp_basal.json
  34. 26 0
      FreeAPS/Sources/Application/FreeAPSApp.swift
  35. 9 0
      FreeAPS/Sources/Containers/NetworkContainer.swift
  36. 7 0
      FreeAPS/Sources/Containers/SecurityContainer.swift
  37. 23 0
      FreeAPS/Sources/Containers/StorageContainer.swift
  38. 8 0
      FreeAPS/Sources/Containers/UIContainer.swift
  39. 9 0
      FreeAPS/Sources/Helpers/CheckBox.swift
  40. 82 0
      FreeAPS/Sources/Helpers/FlowStack.swift
  41. 17 0
      FreeAPS/Sources/Helpers/Formatters.swift
  42. 40 0
      FreeAPS/Sources/Helpers/Injected.swift
  43. 0 0
      FreeAPS/Sources/Helpers/JSON.swift
  44. 19 0
      FreeAPS/Sources/Helpers/Persisted.swift
  45. 25 0
      FreeAPS/Sources/Helpers/ProgressBar.swift
  46. 29 0
      FreeAPS/Sources/Helpers/Publisher.swift
  47. 110 0
      FreeAPS/Sources/Helpers/ViewModifiers.swift
  48. 0 0
      FreeAPS/Sources/Models/Autosens.swift
  49. 0 0
      FreeAPS/Sources/Models/Profile.swift
  50. 7 0
      FreeAPS/Sources/Models/User.swift
  51. 3 0
      FreeAPS/Sources/Modules/AuthotizedRoot/AuthotizedRootBuilder.swift
  52. 18 0
      FreeAPS/Sources/Modules/AuthotizedRoot/AuthotizedRootDataFlow.swift
  53. 5 0
      FreeAPS/Sources/Modules/AuthotizedRoot/AuthotizedRootProvider.swift
  54. 26 0
      FreeAPS/Sources/Modules/AuthotizedRoot/AuthotizedRootViewModel.swift
  55. 24 0
      FreeAPS/Sources/Modules/AuthotizedRoot/View/AuthotizedRootRootView.swift
  56. 25 0
      FreeAPS/Sources/Modules/Base/BaseModuleBuilder.swift
  57. 29 0
      FreeAPS/Sources/Modules/Base/BaseProvider.swift
  58. 11 0
      FreeAPS/Sources/Modules/Base/BaseView.swift
  59. 38 0
      FreeAPS/Sources/Modules/Base/BaseViewModel.swift
  60. 3 0
      FreeAPS/Sources/Modules/Home/HomeBuilder.swift
  61. 5 0
      FreeAPS/Sources/Modules/Home/HomeDataFlow.swift
  62. 3 0
      FreeAPS/Sources/Modules/Home/HomeProvider.swift
  63. 9 0
      FreeAPS/Sources/Modules/Home/HomeViewModel.swift
  64. 22 0
      FreeAPS/Sources/Modules/Home/View/HomeRootView.swift
  65. 3 0
      FreeAPS/Sources/Modules/Login/LoginBuilder.swift
  66. 36 0
      FreeAPS/Sources/Modules/Login/LoginDataFlow.swift
  67. 20 0
      FreeAPS/Sources/Modules/Login/LoginProvider.swift
  68. 17 0
      FreeAPS/Sources/Modules/Login/LoginViewModel.swift
  69. 30 0
      FreeAPS/Sources/Modules/Login/View/LoginRootView.swift
  70. 5 0
      FreeAPS/Sources/Modules/Main/MainBuilder.swift
  71. 30 0
      FreeAPS/Sources/Modules/Main/MainDataFlow.swift
  72. 7 0
      FreeAPS/Sources/Modules/Main/MainProvider.swift
  73. 49 0
      FreeAPS/Sources/Modules/Main/MainViewModel.swift
  74. 14 0
      FreeAPS/Sources/Modules/Main/View/MainRootView.swift
  75. 3 0
      FreeAPS/Sources/Modules/Onboarding/OnboardingBuilder.swift
  76. 19 0
      FreeAPS/Sources/Modules/Onboarding/OnboardingDataFlow.swift
  77. 3 0
      FreeAPS/Sources/Modules/Onboarding/OnboardingProvider.swift
  78. 13 0
      FreeAPS/Sources/Modules/Onboarding/OnboardingViewModel.swift
  79. 11 0
      FreeAPS/Sources/Modules/Onboarding/View/OnboardingRootView.swift
  80. 3 0
      FreeAPS/Sources/Modules/RequestPermissions/RequestPermissionsBuilder.swift
  81. 5 0
      FreeAPS/Sources/Modules/RequestPermissions/RequestPermissionsDataFlow.swift
  82. 3 0
      FreeAPS/Sources/Modules/RequestPermissions/RequestPermissionsProvider.swift
  83. 5 0
      FreeAPS/Sources/Modules/RequestPermissions/RequestPermissionsViewModel.swift
  84. 13 0
      FreeAPS/Sources/Modules/RequestPermissions/View/RequestPermissionsRootView.swift
  85. 3 0
      FreeAPS/Sources/Modules/Settings/SettingsBuilder.swift
  86. 5 0
      FreeAPS/Sources/Modules/Settings/SettingsDataFlow.swift
  87. 9 0
      FreeAPS/Sources/Modules/Settings/SettingsProvider.swift
  88. 5 0
      FreeAPS/Sources/Modules/Settings/SettingsViewModel.swift
  89. 16 0
      FreeAPS/Sources/Modules/Settings/View/SettingsRootView.swift
  90. 0 0
      FreeAPS/Sources/OpenAPS/JavaScriptWorker.swift
  91. 0 0
      FreeAPS/Sources/OpenAPS/OpenAPS.swift
  92. 0 0
      FreeAPS/Sources/OpenAPS/Script.swift
  93. 36 0
      FreeAPS/Sources/Router/Router.swift
  94. 58 0
      FreeAPS/Sources/Router/Screen.swift
  95. 12 0
      FreeAPS/Sources/Services/Appearance/AppearanceManager.swift
  96. 42 0
      FreeAPS/Sources/Services/AuthorizationManager/AuthorizationManager.swift
  97. 496 0
      FreeAPS/Sources/Services/Network/HTTPResponseStatus.swift
  98. 27 0
      FreeAPS/Sources/Services/Network/NetworkManager.swift
  99. 62 0
      FreeAPS/Sources/Services/Network/RemoteService.swift
  100. 0 0
      FreeAPS/Sources/Services/Storage/Cache/Cache.swift

+ 575 - 15
FreeAPS.xcodeproj/project.pbxproj

@@ -3,14 +3,84 @@
 	archiveVersion = 1;
 	classes = {
 	};
-	objectVersion = 50;
+	objectVersion = 52;
 	objects = {
 
 /* Begin PBXBuildFile section */
+		3811DE0925C9D32F00A708ED /* BaseViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DE0525C9D32E00A708ED /* BaseViewModel.swift */; };
+		3811DE0A25C9D32F00A708ED /* BaseModuleBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DE0625C9D32E00A708ED /* BaseModuleBuilder.swift */; };
+		3811DE0B25C9D32F00A708ED /* BaseView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DE0725C9D32E00A708ED /* BaseView.swift */; };
+		3811DE0C25C9D32F00A708ED /* BaseProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DE0825C9D32F00A708ED /* BaseProvider.swift */; };
+		3811DE1025C9D37700A708ED /* Swinject in Frameworks */ = {isa = PBXBuildFile; productRef = 3811DE0F25C9D37700A708ED /* Swinject */; };
+		3811DE1725C9D40400A708ED /* Screen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DE1525C9D40400A708ED /* Screen.swift */; };
+		3811DE1825C9D40400A708ED /* Router.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DE1625C9D40400A708ED /* Router.swift */; };
+		3811DE2125C9D48300A708ED /* MainBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DE1B25C9D48300A708ED /* MainBuilder.swift */; };
+		3811DE2225C9D48300A708ED /* MainProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DE1C25C9D48300A708ED /* MainProvider.swift */; };
+		3811DE2325C9D48300A708ED /* MainDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DE1D25C9D48300A708ED /* MainDataFlow.swift */; };
+		3811DE2425C9D48300A708ED /* MainViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DE1E25C9D48300A708ED /* MainViewModel.swift */; };
+		3811DE2525C9D48300A708ED /* MainRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DE2025C9D48300A708ED /* MainRootView.swift */; };
+		3811DE3025C9D49500A708ED /* HomeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DE2825C9D49500A708ED /* HomeViewModel.swift */; };
+		3811DE3125C9D49500A708ED /* HomeProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DE2925C9D49500A708ED /* HomeProvider.swift */; };
+		3811DE3225C9D49500A708ED /* HomeDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DE2A25C9D49500A708ED /* HomeDataFlow.swift */; };
+		3811DE3325C9D49500A708ED /* HomeBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DE2B25C9D49500A708ED /* HomeBuilder.swift */; };
+		3811DE3525C9D49500A708ED /* HomeRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DE2E25C9D49500A708ED /* HomeRootView.swift */; };
+		3811DE3F25C9D4A100A708ED /* SettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DE3925C9D4A100A708ED /* SettingsViewModel.swift */; };
+		3811DE4025C9D4A100A708ED /* SettingsBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DE3A25C9D4A100A708ED /* SettingsBuilder.swift */; };
+		3811DE4125C9D4A100A708ED /* SettingsRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DE3C25C9D4A100A708ED /* SettingsRootView.swift */; };
+		3811DE4225C9D4A100A708ED /* SettingsDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DE3D25C9D4A100A708ED /* SettingsDataFlow.swift */; };
+		3811DE4325C9D4A100A708ED /* SettingsProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DE3E25C9D4A100A708ED /* SettingsProvider.swift */; };
+		3811DE4C25C9D4B800A708ED /* AuthotizedRootBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DE4625C9D4B800A708ED /* AuthotizedRootBuilder.swift */; };
+		3811DE4D25C9D4B800A708ED /* AuthotizedRootViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DE4725C9D4B800A708ED /* AuthotizedRootViewModel.swift */; };
+		3811DE4E25C9D4B800A708ED /* AuthotizedRootRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DE4925C9D4B800A708ED /* AuthotizedRootRootView.swift */; };
+		3811DE4F25C9D4B800A708ED /* AuthotizedRootDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DE4A25C9D4B800A708ED /* AuthotizedRootDataFlow.swift */; };
+		3811DE5025C9D4B800A708ED /* AuthotizedRootProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DE4B25C9D4B800A708ED /* AuthotizedRootProvider.swift */; };
+		3811DE5A25C9D4D500A708ED /* CheckBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DE5225C9D4D500A708ED /* CheckBox.swift */; };
+		3811DE5B25C9D4D500A708ED /* Persisted.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DE5325C9D4D500A708ED /* Persisted.swift */; };
+		3811DE5C25C9D4D500A708ED /* Formatters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DE5425C9D4D500A708ED /* Formatters.swift */; };
+		3811DE5D25C9D4D500A708ED /* Publisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DE5525C9D4D500A708ED /* Publisher.swift */; };
+		3811DE5E25C9D4D500A708ED /* FlowStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DE5625C9D4D500A708ED /* FlowStack.swift */; };
+		3811DE5F25C9D4D500A708ED /* ProgressBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DE5725C9D4D500A708ED /* ProgressBar.swift */; };
+		3811DE6025C9D4D500A708ED /* Injected.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DE5825C9D4D500A708ED /* Injected.swift */; };
+		3811DE6125C9D4D500A708ED /* ViewModifiers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DE5925C9D4D500A708ED /* ViewModifiers.swift */; };
+		3811DE6A25C9D62600A708ED /* OnboardingBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DE6425C9D62600A708ED /* OnboardingBuilder.swift */; };
+		3811DE6B25C9D62600A708ED /* OnboardingProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DE6525C9D62600A708ED /* OnboardingProvider.swift */; };
+		3811DE6C25C9D62600A708ED /* OnboardingDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DE6625C9D62600A708ED /* OnboardingDataFlow.swift */; };
+		3811DE6D25C9D62600A708ED /* OnboardingRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DE6825C9D62600A708ED /* OnboardingRootView.swift */; };
+		3811DE6E25C9D62600A708ED /* OnboardingViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DE6925C9D62600A708ED /* OnboardingViewModel.swift */; };
+		3811DE7925C9D6D300A708ED /* LoginViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DE7125C9D6D300A708ED /* LoginViewModel.swift */; };
+		3811DE7A25C9D6D300A708ED /* LoginDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DE7225C9D6D300A708ED /* LoginDataFlow.swift */; };
+		3811DE7B25C9D6D300A708ED /* LoginProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DE7325C9D6D300A708ED /* LoginProvider.swift */; };
+		3811DE7D25C9D6D300A708ED /* LoginRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DE7625C9D6D300A708ED /* LoginRootView.swift */; };
+		3811DE7F25C9D6D300A708ED /* LoginBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DE7825C9D6D300A708ED /* LoginBuilder.swift */; };
+		3811DE8825C9D6DD00A708ED /* RequestPermissionsDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DE8225C9D6DD00A708ED /* RequestPermissionsDataFlow.swift */; };
+		3811DE8925C9D6DD00A708ED /* RequestPermissionsProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DE8325C9D6DD00A708ED /* RequestPermissionsProvider.swift */; };
+		3811DE8A25C9D6DD00A708ED /* RequestPermissionsRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DE8525C9D6DD00A708ED /* RequestPermissionsRootView.swift */; };
+		3811DE8B25C9D6DD00A708ED /* RequestPermissionsBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DE8625C9D6DD00A708ED /* RequestPermissionsBuilder.swift */; };
+		3811DE8C25C9D6DD00A708ED /* RequestPermissionsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DE8725C9D6DD00A708ED /* RequestPermissionsViewModel.swift */; };
+		3811DE8F25C9D80400A708ED /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DE8E25C9D80400A708ED /* User.swift */; };
+		3811DEA925C9D88300A708ED /* AppearanceManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DE9325C9D88200A708ED /* AppearanceManager.swift */; };
+		3811DEAA25C9D88300A708ED /* RemoteService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DE9525C9D88200A708ED /* RemoteService.swift */; };
+		3811DEAB25C9D88300A708ED /* HTTPResponseStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DE9625C9D88300A708ED /* HTTPResponseStatus.swift */; };
+		3811DEAC25C9D88300A708ED /* NetworkManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DE9725C9D88300A708ED /* NetworkManager.swift */; };
+		3811DEAD25C9D88300A708ED /* UserDefaults+Cache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DE9A25C9D88300A708ED /* UserDefaults+Cache.swift */; };
+		3811DEAE25C9D88300A708ED /* Cache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DE9B25C9D88300A708ED /* Cache.swift */; };
+		3811DEAF25C9D88300A708ED /* KeyValueStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DE9C25C9D88300A708ED /* KeyValueStorage.swift */; };
+		3811DEB025C9D88300A708ED /* BaseKeychain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DE9E25C9D88300A708ED /* BaseKeychain.swift */; };
+		3811DEB125C9D88300A708ED /* Keychain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DE9F25C9D88300A708ED /* Keychain.swift */; };
+		3811DEB225C9D88300A708ED /* KeychainItemAccessibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DEA025C9D88300A708ED /* KeychainItemAccessibility.swift */; };
+		3811DEB325C9D88300A708ED /* ImageFileStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DEA225C9D88300A708ED /* ImageFileStorage.swift */; };
+		3811DEB425C9D88300A708ED /* FileManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DEA325C9D88300A708ED /* FileManager.swift */; };
+		3811DEB525C9D88300A708ED /* FileStorageError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DEA425C9D88300A708ED /* FileStorageError.swift */; };
+		3811DEB625C9D88300A708ED /* UnlockManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DEA625C9D88300A708ED /* UnlockManager.swift */; };
+		3811DEB725C9D88300A708ED /* AuthorizationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DEA825C9D88300A708ED /* AuthorizationManager.swift */; };
+		3811DEBB25C9D8DD00A708ED /* Moya in Frameworks */ = {isa = PBXBuildFile; productRef = 3811DEBA25C9D8DD00A708ED /* Moya */; };
+		3811DEC225C9D99900A708ED /* SecurityContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DEBE25C9D99900A708ED /* SecurityContainer.swift */; };
+		3811DEC325C9D99900A708ED /* UIContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DEBF25C9D99900A708ED /* UIContainer.swift */; };
+		3811DEC425C9D99900A708ED /* NetworkContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DEC025C9D99900A708ED /* NetworkContainer.swift */; };
+		3811DEC525C9D99900A708ED /* StorageContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DEC125C9D99900A708ED /* StorageContainer.swift */; };
 		384E803425C385E60086DB71 /* JavaScriptWorker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 384E803325C385E60086DB71 /* JavaScriptWorker.swift */; };
 		384E803825C388640086DB71 /* Script.swift in Sources */ = {isa = PBXBuildFile; fileRef = 384E803725C388640086DB71 /* Script.swift */; };
 		388E595C25AD948C0019842D /* FreeAPSApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 388E595B25AD948C0019842D /* FreeAPSApp.swift */; };
-		388E595E25AD948C0019842D /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 388E595D25AD948C0019842D /* ContentView.swift */; };
 		388E596025AD948E0019842D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 388E595F25AD948E0019842D /* Assets.xcassets */; };
 		388E596325AD948E0019842D /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 388E596225AD948E0019842D /* Preview Assets.xcassets */; };
 		388E596C25AD95110019842D /* OpenAPS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 388E596B25AD95110019842D /* OpenAPS.swift */; };
@@ -22,11 +92,80 @@
 /* End PBXBuildFile section */
 
 /* Begin PBXFileReference section */
+		3811DE0525C9D32E00A708ED /* BaseViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BaseViewModel.swift; sourceTree = "<group>"; };
+		3811DE0625C9D32E00A708ED /* BaseModuleBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BaseModuleBuilder.swift; sourceTree = "<group>"; };
+		3811DE0725C9D32E00A708ED /* BaseView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BaseView.swift; sourceTree = "<group>"; };
+		3811DE0825C9D32F00A708ED /* BaseProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BaseProvider.swift; sourceTree = "<group>"; };
+		3811DE1525C9D40400A708ED /* Screen.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Screen.swift; sourceTree = "<group>"; };
+		3811DE1625C9D40400A708ED /* Router.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Router.swift; sourceTree = "<group>"; };
+		3811DE1B25C9D48300A708ED /* MainBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainBuilder.swift; sourceTree = "<group>"; };
+		3811DE1C25C9D48300A708ED /* MainProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainProvider.swift; sourceTree = "<group>"; };
+		3811DE1D25C9D48300A708ED /* MainDataFlow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainDataFlow.swift; sourceTree = "<group>"; };
+		3811DE1E25C9D48300A708ED /* MainViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainViewModel.swift; sourceTree = "<group>"; };
+		3811DE2025C9D48300A708ED /* MainRootView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainRootView.swift; sourceTree = "<group>"; };
+		3811DE2825C9D49500A708ED /* HomeViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeViewModel.swift; sourceTree = "<group>"; };
+		3811DE2925C9D49500A708ED /* HomeProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeProvider.swift; sourceTree = "<group>"; };
+		3811DE2A25C9D49500A708ED /* HomeDataFlow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeDataFlow.swift; sourceTree = "<group>"; };
+		3811DE2B25C9D49500A708ED /* HomeBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeBuilder.swift; sourceTree = "<group>"; };
+		3811DE2E25C9D49500A708ED /* HomeRootView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeRootView.swift; sourceTree = "<group>"; };
+		3811DE3925C9D4A100A708ED /* SettingsViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsViewModel.swift; sourceTree = "<group>"; };
+		3811DE3A25C9D4A100A708ED /* SettingsBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsBuilder.swift; sourceTree = "<group>"; };
+		3811DE3C25C9D4A100A708ED /* SettingsRootView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsRootView.swift; sourceTree = "<group>"; };
+		3811DE3D25C9D4A100A708ED /* SettingsDataFlow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsDataFlow.swift; sourceTree = "<group>"; };
+		3811DE3E25C9D4A100A708ED /* SettingsProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsProvider.swift; sourceTree = "<group>"; };
+		3811DE4625C9D4B800A708ED /* AuthotizedRootBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthotizedRootBuilder.swift; sourceTree = "<group>"; };
+		3811DE4725C9D4B800A708ED /* AuthotizedRootViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthotizedRootViewModel.swift; sourceTree = "<group>"; };
+		3811DE4925C9D4B800A708ED /* AuthotizedRootRootView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthotizedRootRootView.swift; sourceTree = "<group>"; };
+		3811DE4A25C9D4B800A708ED /* AuthotizedRootDataFlow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthotizedRootDataFlow.swift; sourceTree = "<group>"; };
+		3811DE4B25C9D4B800A708ED /* AuthotizedRootProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthotizedRootProvider.swift; sourceTree = "<group>"; };
+		3811DE5225C9D4D500A708ED /* CheckBox.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CheckBox.swift; sourceTree = "<group>"; };
+		3811DE5325C9D4D500A708ED /* Persisted.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Persisted.swift; sourceTree = "<group>"; };
+		3811DE5425C9D4D500A708ED /* Formatters.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Formatters.swift; sourceTree = "<group>"; };
+		3811DE5525C9D4D500A708ED /* Publisher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Publisher.swift; sourceTree = "<group>"; };
+		3811DE5625C9D4D500A708ED /* FlowStack.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FlowStack.swift; sourceTree = "<group>"; };
+		3811DE5725C9D4D500A708ED /* ProgressBar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProgressBar.swift; sourceTree = "<group>"; };
+		3811DE5825C9D4D500A708ED /* Injected.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Injected.swift; sourceTree = "<group>"; };
+		3811DE5925C9D4D500A708ED /* ViewModifiers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewModifiers.swift; sourceTree = "<group>"; };
+		3811DE6425C9D62600A708ED /* OnboardingBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnboardingBuilder.swift; sourceTree = "<group>"; };
+		3811DE6525C9D62600A708ED /* OnboardingProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnboardingProvider.swift; sourceTree = "<group>"; };
+		3811DE6625C9D62600A708ED /* OnboardingDataFlow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnboardingDataFlow.swift; sourceTree = "<group>"; };
+		3811DE6825C9D62600A708ED /* OnboardingRootView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnboardingRootView.swift; sourceTree = "<group>"; };
+		3811DE6925C9D62600A708ED /* OnboardingViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnboardingViewModel.swift; sourceTree = "<group>"; };
+		3811DE7125C9D6D300A708ED /* LoginViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginViewModel.swift; sourceTree = "<group>"; };
+		3811DE7225C9D6D300A708ED /* LoginDataFlow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginDataFlow.swift; sourceTree = "<group>"; };
+		3811DE7325C9D6D300A708ED /* LoginProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginProvider.swift; sourceTree = "<group>"; };
+		3811DE7625C9D6D300A708ED /* LoginRootView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginRootView.swift; sourceTree = "<group>"; };
+		3811DE7825C9D6D300A708ED /* LoginBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginBuilder.swift; sourceTree = "<group>"; };
+		3811DE8225C9D6DD00A708ED /* RequestPermissionsDataFlow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RequestPermissionsDataFlow.swift; sourceTree = "<group>"; };
+		3811DE8325C9D6DD00A708ED /* RequestPermissionsProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RequestPermissionsProvider.swift; sourceTree = "<group>"; };
+		3811DE8525C9D6DD00A708ED /* RequestPermissionsRootView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RequestPermissionsRootView.swift; sourceTree = "<group>"; };
+		3811DE8625C9D6DD00A708ED /* RequestPermissionsBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RequestPermissionsBuilder.swift; sourceTree = "<group>"; };
+		3811DE8725C9D6DD00A708ED /* RequestPermissionsViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RequestPermissionsViewModel.swift; sourceTree = "<group>"; };
+		3811DE8E25C9D80400A708ED /* User.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = User.swift; sourceTree = "<group>"; };
+		3811DE9325C9D88200A708ED /* AppearanceManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppearanceManager.swift; sourceTree = "<group>"; };
+		3811DE9525C9D88200A708ED /* RemoteService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RemoteService.swift; sourceTree = "<group>"; };
+		3811DE9625C9D88300A708ED /* HTTPResponseStatus.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTTPResponseStatus.swift; sourceTree = "<group>"; };
+		3811DE9725C9D88300A708ED /* NetworkManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkManager.swift; sourceTree = "<group>"; };
+		3811DE9A25C9D88300A708ED /* UserDefaults+Cache.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UserDefaults+Cache.swift"; sourceTree = "<group>"; };
+		3811DE9B25C9D88300A708ED /* Cache.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Cache.swift; sourceTree = "<group>"; };
+		3811DE9C25C9D88300A708ED /* KeyValueStorage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyValueStorage.swift; sourceTree = "<group>"; };
+		3811DE9E25C9D88300A708ED /* BaseKeychain.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BaseKeychain.swift; sourceTree = "<group>"; };
+		3811DE9F25C9D88300A708ED /* Keychain.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Keychain.swift; sourceTree = "<group>"; };
+		3811DEA025C9D88300A708ED /* KeychainItemAccessibility.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeychainItemAccessibility.swift; sourceTree = "<group>"; };
+		3811DEA225C9D88300A708ED /* ImageFileStorage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageFileStorage.swift; sourceTree = "<group>"; };
+		3811DEA325C9D88300A708ED /* FileManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileManager.swift; sourceTree = "<group>"; };
+		3811DEA425C9D88300A708ED /* FileStorageError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileStorageError.swift; sourceTree = "<group>"; };
+		3811DEA625C9D88300A708ED /* UnlockManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnlockManager.swift; sourceTree = "<group>"; };
+		3811DEA825C9D88300A708ED /* AuthorizationManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthorizationManager.swift; sourceTree = "<group>"; };
+		3811DEBE25C9D99900A708ED /* SecurityContainer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecurityContainer.swift; sourceTree = "<group>"; };
+		3811DEBF25C9D99900A708ED /* UIContainer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIContainer.swift; sourceTree = "<group>"; };
+		3811DEC025C9D99900A708ED /* NetworkContainer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkContainer.swift; sourceTree = "<group>"; };
+		3811DEC125C9D99900A708ED /* StorageContainer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StorageContainer.swift; sourceTree = "<group>"; };
+		3811DEC725C9DA7300A708ED /* FreeAPS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = FreeAPS.entitlements; sourceTree = "<group>"; };
 		384E803325C385E60086DB71 /* JavaScriptWorker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JavaScriptWorker.swift; sourceTree = "<group>"; };
 		384E803725C388640086DB71 /* Script.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Script.swift; sourceTree = "<group>"; };
 		388E595825AD948C0019842D /* FreeAPS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = FreeAPS.app; sourceTree = BUILT_PRODUCTS_DIR; };
 		388E595B25AD948C0019842D /* FreeAPSApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FreeAPSApp.swift; sourceTree = "<group>"; };
-		388E595D25AD948C0019842D /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
 		388E595F25AD948E0019842D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
 		388E596225AD948E0019842D /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
 		388E596425AD948E0019842D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
@@ -43,18 +182,325 @@
 			isa = PBXFrameworksBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
+				3811DEBB25C9D8DD00A708ED /* Moya in Frameworks */,
+				3811DE1025C9D37700A708ED /* Swinject in Frameworks */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
 /* End PBXFrameworksBuildPhase section */
 
 /* Begin PBXGroup section */
+		3811DE0325C9D31700A708ED /* Modules */ = {
+			isa = PBXGroup;
+			children = (
+				3811DE4525C9D4B800A708ED /* AuthotizedRoot */,
+				3811DE0425C9D32E00A708ED /* Base */,
+				3811DE2725C9D49500A708ED /* Home */,
+				3811DE7025C9D6D300A708ED /* Login */,
+				3811DE1A25C9D48300A708ED /* Main */,
+				3811DE6325C9D62600A708ED /* Onboarding */,
+				3811DE8125C9D6DD00A708ED /* RequestPermissions */,
+				3811DE3825C9D4A100A708ED /* Settings */,
+			);
+			path = Modules;
+			sourceTree = "<group>";
+		};
+		3811DE0425C9D32E00A708ED /* Base */ = {
+			isa = PBXGroup;
+			children = (
+				3811DE0525C9D32E00A708ED /* BaseViewModel.swift */,
+				3811DE0625C9D32E00A708ED /* BaseModuleBuilder.swift */,
+				3811DE0725C9D32E00A708ED /* BaseView.swift */,
+				3811DE0825C9D32F00A708ED /* BaseProvider.swift */,
+			);
+			path = Base;
+			sourceTree = "<group>";
+		};
+		3811DE1325C9D39E00A708ED /* Sources */ = {
+			isa = PBXGroup;
+			children = (
+				3811DEDE25C9E2DD00A708ED /* Application */,
+				3811DEBD25C9D99900A708ED /* Containers */,
+				388E5A5A25B6F05F0019842D /* Helpers */,
+				388E5A5925B6F0250019842D /* Models */,
+				3811DE0325C9D31700A708ED /* Modules */,
+				388E5A5825B6F0070019842D /* OpenAPS */,
+				3811DE1425C9D40400A708ED /* Router */,
+				3811DE9125C9D88200A708ED /* Services */,
+			);
+			path = Sources;
+			sourceTree = "<group>";
+		};
+		3811DE1425C9D40400A708ED /* Router */ = {
+			isa = PBXGroup;
+			children = (
+				3811DE1525C9D40400A708ED /* Screen.swift */,
+				3811DE1625C9D40400A708ED /* Router.swift */,
+			);
+			path = Router;
+			sourceTree = "<group>";
+		};
+		3811DE1A25C9D48300A708ED /* Main */ = {
+			isa = PBXGroup;
+			children = (
+				3811DE1B25C9D48300A708ED /* MainBuilder.swift */,
+				3811DE1C25C9D48300A708ED /* MainProvider.swift */,
+				3811DE1D25C9D48300A708ED /* MainDataFlow.swift */,
+				3811DE1E25C9D48300A708ED /* MainViewModel.swift */,
+				3811DE1F25C9D48300A708ED /* View */,
+			);
+			path = Main;
+			sourceTree = "<group>";
+		};
+		3811DE1F25C9D48300A708ED /* View */ = {
+			isa = PBXGroup;
+			children = (
+				3811DE2025C9D48300A708ED /* MainRootView.swift */,
+			);
+			path = View;
+			sourceTree = "<group>";
+		};
+		3811DE2725C9D49500A708ED /* Home */ = {
+			isa = PBXGroup;
+			children = (
+				3811DE2825C9D49500A708ED /* HomeViewModel.swift */,
+				3811DE2925C9D49500A708ED /* HomeProvider.swift */,
+				3811DE2A25C9D49500A708ED /* HomeDataFlow.swift */,
+				3811DE2B25C9D49500A708ED /* HomeBuilder.swift */,
+				3811DE2C25C9D49500A708ED /* View */,
+			);
+			path = Home;
+			sourceTree = "<group>";
+		};
+		3811DE2C25C9D49500A708ED /* View */ = {
+			isa = PBXGroup;
+			children = (
+				3811DE2E25C9D49500A708ED /* HomeRootView.swift */,
+			);
+			path = View;
+			sourceTree = "<group>";
+		};
+		3811DE3825C9D4A100A708ED /* Settings */ = {
+			isa = PBXGroup;
+			children = (
+				3811DE3A25C9D4A100A708ED /* SettingsBuilder.swift */,
+				3811DE3D25C9D4A100A708ED /* SettingsDataFlow.swift */,
+				3811DE3E25C9D4A100A708ED /* SettingsProvider.swift */,
+				3811DE3925C9D4A100A708ED /* SettingsViewModel.swift */,
+				3811DE3B25C9D4A100A708ED /* View */,
+			);
+			path = Settings;
+			sourceTree = "<group>";
+		};
+		3811DE3B25C9D4A100A708ED /* View */ = {
+			isa = PBXGroup;
+			children = (
+				3811DE3C25C9D4A100A708ED /* SettingsRootView.swift */,
+			);
+			path = View;
+			sourceTree = "<group>";
+		};
+		3811DE4525C9D4B800A708ED /* AuthotizedRoot */ = {
+			isa = PBXGroup;
+			children = (
+				3811DE4625C9D4B800A708ED /* AuthotizedRootBuilder.swift */,
+				3811DE4A25C9D4B800A708ED /* AuthotizedRootDataFlow.swift */,
+				3811DE4B25C9D4B800A708ED /* AuthotizedRootProvider.swift */,
+				3811DE4725C9D4B800A708ED /* AuthotizedRootViewModel.swift */,
+				3811DE4825C9D4B800A708ED /* View */,
+			);
+			path = AuthotizedRoot;
+			sourceTree = "<group>";
+		};
+		3811DE4825C9D4B800A708ED /* View */ = {
+			isa = PBXGroup;
+			children = (
+				3811DE4925C9D4B800A708ED /* AuthotizedRootRootView.swift */,
+			);
+			path = View;
+			sourceTree = "<group>";
+		};
+		3811DE6325C9D62600A708ED /* Onboarding */ = {
+			isa = PBXGroup;
+			children = (
+				3811DE6425C9D62600A708ED /* OnboardingBuilder.swift */,
+				3811DE6525C9D62600A708ED /* OnboardingProvider.swift */,
+				3811DE6625C9D62600A708ED /* OnboardingDataFlow.swift */,
+				3811DE6725C9D62600A708ED /* View */,
+				3811DE6925C9D62600A708ED /* OnboardingViewModel.swift */,
+			);
+			path = Onboarding;
+			sourceTree = "<group>";
+		};
+		3811DE6725C9D62600A708ED /* View */ = {
+			isa = PBXGroup;
+			children = (
+				3811DE6825C9D62600A708ED /* OnboardingRootView.swift */,
+			);
+			path = View;
+			sourceTree = "<group>";
+		};
+		3811DE7025C9D6D300A708ED /* Login */ = {
+			isa = PBXGroup;
+			children = (
+				3811DE7825C9D6D300A708ED /* LoginBuilder.swift */,
+				3811DE7225C9D6D300A708ED /* LoginDataFlow.swift */,
+				3811DE7325C9D6D300A708ED /* LoginProvider.swift */,
+				3811DE7125C9D6D300A708ED /* LoginViewModel.swift */,
+				3811DE7425C9D6D300A708ED /* View */,
+			);
+			path = Login;
+			sourceTree = "<group>";
+		};
+		3811DE7425C9D6D300A708ED /* View */ = {
+			isa = PBXGroup;
+			children = (
+				3811DE7625C9D6D300A708ED /* LoginRootView.swift */,
+			);
+			path = View;
+			sourceTree = "<group>";
+		};
+		3811DE8125C9D6DD00A708ED /* RequestPermissions */ = {
+			isa = PBXGroup;
+			children = (
+				3811DE8225C9D6DD00A708ED /* RequestPermissionsDataFlow.swift */,
+				3811DE8325C9D6DD00A708ED /* RequestPermissionsProvider.swift */,
+				3811DE8425C9D6DD00A708ED /* View */,
+				3811DE8625C9D6DD00A708ED /* RequestPermissionsBuilder.swift */,
+				3811DE8725C9D6DD00A708ED /* RequestPermissionsViewModel.swift */,
+			);
+			path = RequestPermissions;
+			sourceTree = "<group>";
+		};
+		3811DE8425C9D6DD00A708ED /* View */ = {
+			isa = PBXGroup;
+			children = (
+				3811DE8525C9D6DD00A708ED /* RequestPermissionsRootView.swift */,
+			);
+			path = View;
+			sourceTree = "<group>";
+		};
+		3811DE9125C9D88200A708ED /* Services */ = {
+			isa = PBXGroup;
+			children = (
+				3811DE9225C9D88200A708ED /* Appearance */,
+				3811DE9425C9D88200A708ED /* Network */,
+				3811DE9825C9D88300A708ED /* Storage */,
+				3811DEA525C9D88300A708ED /* UnlockManager */,
+				3811DEA725C9D88300A708ED /* AuthorizationManager */,
+			);
+			path = Services;
+			sourceTree = "<group>";
+		};
+		3811DE9225C9D88200A708ED /* Appearance */ = {
+			isa = PBXGroup;
+			children = (
+				3811DE9325C9D88200A708ED /* AppearanceManager.swift */,
+			);
+			path = Appearance;
+			sourceTree = "<group>";
+		};
+		3811DE9425C9D88200A708ED /* Network */ = {
+			isa = PBXGroup;
+			children = (
+				3811DE9525C9D88200A708ED /* RemoteService.swift */,
+				3811DE9625C9D88300A708ED /* HTTPResponseStatus.swift */,
+				3811DE9725C9D88300A708ED /* NetworkManager.swift */,
+			);
+			path = Network;
+			sourceTree = "<group>";
+		};
+		3811DE9825C9D88300A708ED /* Storage */ = {
+			isa = PBXGroup;
+			children = (
+				3811DE9925C9D88300A708ED /* Cache */,
+				3811DE9C25C9D88300A708ED /* KeyValueStorage.swift */,
+				3811DE9D25C9D88300A708ED /* Keychain */,
+				3811DEA125C9D88300A708ED /* ImageFileStorage */,
+			);
+			path = Storage;
+			sourceTree = "<group>";
+		};
+		3811DE9925C9D88300A708ED /* Cache */ = {
+			isa = PBXGroup;
+			children = (
+				3811DE9A25C9D88300A708ED /* UserDefaults+Cache.swift */,
+				3811DE9B25C9D88300A708ED /* Cache.swift */,
+			);
+			path = Cache;
+			sourceTree = "<group>";
+		};
+		3811DE9D25C9D88300A708ED /* Keychain */ = {
+			isa = PBXGroup;
+			children = (
+				3811DE9E25C9D88300A708ED /* BaseKeychain.swift */,
+				3811DE9F25C9D88300A708ED /* Keychain.swift */,
+				3811DEA025C9D88300A708ED /* KeychainItemAccessibility.swift */,
+			);
+			path = Keychain;
+			sourceTree = "<group>";
+		};
+		3811DEA125C9D88300A708ED /* ImageFileStorage */ = {
+			isa = PBXGroup;
+			children = (
+				3811DEA225C9D88300A708ED /* ImageFileStorage.swift */,
+				3811DEA325C9D88300A708ED /* FileManager.swift */,
+				3811DEA425C9D88300A708ED /* FileStorageError.swift */,
+			);
+			path = ImageFileStorage;
+			sourceTree = "<group>";
+		};
+		3811DEA525C9D88300A708ED /* UnlockManager */ = {
+			isa = PBXGroup;
+			children = (
+				3811DEA625C9D88300A708ED /* UnlockManager.swift */,
+			);
+			path = UnlockManager;
+			sourceTree = "<group>";
+		};
+		3811DEA725C9D88300A708ED /* AuthorizationManager */ = {
+			isa = PBXGroup;
+			children = (
+				3811DEA825C9D88300A708ED /* AuthorizationManager.swift */,
+			);
+			path = AuthorizationManager;
+			sourceTree = "<group>";
+		};
+		3811DEBD25C9D99900A708ED /* Containers */ = {
+			isa = PBXGroup;
+			children = (
+				3811DEBE25C9D99900A708ED /* SecurityContainer.swift */,
+				3811DEBF25C9D99900A708ED /* UIContainer.swift */,
+				3811DEC025C9D99900A708ED /* NetworkContainer.swift */,
+				3811DEC125C9D99900A708ED /* StorageContainer.swift */,
+			);
+			path = Containers;
+			sourceTree = "<group>";
+		};
+		3811DED425C9E1E300A708ED /* Resources */ = {
+			isa = PBXGroup;
+			children = (
+				388E597125AD9CF10019842D /* json */,
+				388E596E25AD96040019842D /* javascript */,
+				3811DEC725C9DA7300A708ED /* FreeAPS.entitlements */,
+				388E596425AD948E0019842D /* Info.plist */,
+				388E595F25AD948E0019842D /* Assets.xcassets */,
+			);
+			path = Resources;
+			sourceTree = "<group>";
+		};
+		3811DEDE25C9E2DD00A708ED /* Application */ = {
+			isa = PBXGroup;
+			children = (
+				388E595B25AD948C0019842D /* FreeAPSApp.swift */,
+			);
+			path = Application;
+			sourceTree = "<group>";
+		};
 		388E594F25AD948C0019842D = {
 			isa = PBXGroup;
 			children = (
 				388E595A25AD948C0019842D /* FreeAPS */,
-				388E596E25AD96040019842D /* javascript */,
-				388E597125AD9CF10019842D /* json */,
 				388E595925AD948C0019842D /* Products */,
 			);
 			sourceTree = "<group>";
@@ -70,14 +516,9 @@
 		388E595A25AD948C0019842D /* FreeAPS */ = {
 			isa = PBXGroup;
 			children = (
-				388E5A5A25B6F05F0019842D /* Helpers */,
-				388E5A5925B6F0250019842D /* Models */,
-				388E5A5825B6F0070019842D /* OpenAPS */,
-				388E595B25AD948C0019842D /* FreeAPSApp.swift */,
-				388E595D25AD948C0019842D /* ContentView.swift */,
-				388E595F25AD948E0019842D /* Assets.xcassets */,
-				388E596425AD948E0019842D /* Info.plist */,
 				388E596125AD948E0019842D /* Preview Content */,
+				3811DED425C9E1E300A708ED /* Resources */,
+				3811DE1325C9D39E00A708ED /* Sources */,
 			);
 			path = FreeAPS;
 			sourceTree = "<group>";
@@ -103,6 +544,7 @@
 		388E5A5925B6F0250019842D /* Models */ = {
 			isa = PBXGroup;
 			children = (
+				3811DE8E25C9D80400A708ED /* User.swift */,
 				388E5A5F25B6F2310019842D /* Autosens.swift */,
 				3895E4C525B9E00D00214B37 /* Profile.swift */,
 			);
@@ -112,6 +554,14 @@
 		388E5A5A25B6F05F0019842D /* Helpers */ = {
 			isa = PBXGroup;
 			children = (
+				3811DE5225C9D4D500A708ED /* CheckBox.swift */,
+				3811DE5625C9D4D500A708ED /* FlowStack.swift */,
+				3811DE5425C9D4D500A708ED /* Formatters.swift */,
+				3811DE5825C9D4D500A708ED /* Injected.swift */,
+				3811DE5325C9D4D500A708ED /* Persisted.swift */,
+				3811DE5725C9D4D500A708ED /* ProgressBar.swift */,
+				3811DE5525C9D4D500A708ED /* Publisher.swift */,
+				3811DE5925C9D4D500A708ED /* ViewModifiers.swift */,
 				388E5A5B25B6F0770019842D /* JSON.swift */,
 			);
 			path = Helpers;
@@ -133,6 +583,10 @@
 			dependencies = (
 			);
 			name = FreeAPS;
+			packageProductDependencies = (
+				3811DE0F25C9D37700A708ED /* Swinject */,
+				3811DEBA25C9D8DD00A708ED /* Moya */,
+			);
 			productName = FreeAPS;
 			productReference = 388E595825AD948C0019842D /* FreeAPS.app */;
 			productType = "com.apple.product-type.application";
@@ -160,6 +614,10 @@
 				Base,
 			);
 			mainGroup = 388E594F25AD948C0019842D;
+			packageReferences = (
+				3811DE0E25C9D37700A708ED /* XCRemoteSwiftPackageReference "Swinject" */,
+				3811DEB925C9D8DD00A708ED /* XCRemoteSwiftPackageReference "Moya" */,
+			);
 			productRefGroup = 388E595925AD948C0019842D /* Products */;
 			projectDirPath = "";
 			projectRoot = "";
@@ -188,14 +646,82 @@
 			isa = PBXSourcesBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
+				3811DE2325C9D48300A708ED /* MainDataFlow.swift in Sources */,
 				388E5A6025B6F2310019842D /* Autosens.swift in Sources */,
+				3811DE8B25C9D6DD00A708ED /* RequestPermissionsBuilder.swift in Sources */,
+				3811DE4C25C9D4B800A708ED /* AuthotizedRootBuilder.swift in Sources */,
+				3811DE8F25C9D80400A708ED /* User.swift in Sources */,
+				3811DEB225C9D88300A708ED /* KeychainItemAccessibility.swift in Sources */,
+				3811DE6B25C9D62600A708ED /* OnboardingProvider.swift in Sources */,
+				3811DEC225C9D99900A708ED /* SecurityContainer.swift in Sources */,
 				3895E4C625B9E00D00214B37 /* Profile.swift in Sources */,
+				3811DE6E25C9D62600A708ED /* OnboardingViewModel.swift in Sources */,
+				3811DE6D25C9D62600A708ED /* OnboardingRootView.swift in Sources */,
+				3811DEB525C9D88300A708ED /* FileStorageError.swift in Sources */,
+				3811DE3025C9D49500A708ED /* HomeViewModel.swift in Sources */,
+				3811DE0A25C9D32F00A708ED /* BaseModuleBuilder.swift in Sources */,
+				3811DE1725C9D40400A708ED /* Screen.swift in Sources */,
 				388E596C25AD95110019842D /* OpenAPS.swift in Sources */,
-				388E595E25AD948C0019842D /* ContentView.swift in Sources */,
 				384E803825C388640086DB71 /* Script.swift in Sources */,
+				3811DE0925C9D32F00A708ED /* BaseViewModel.swift in Sources */,
+				3811DEB125C9D88300A708ED /* Keychain.swift in Sources */,
+				3811DE7B25C9D6D300A708ED /* LoginProvider.swift in Sources */,
+				3811DEB425C9D88300A708ED /* FileManager.swift in Sources */,
+				3811DE4125C9D4A100A708ED /* SettingsRootView.swift in Sources */,
 				388E595C25AD948C0019842D /* FreeAPSApp.swift in Sources */,
+				3811DE8925C9D6DD00A708ED /* RequestPermissionsProvider.swift in Sources */,
+				3811DE4225C9D4A100A708ED /* SettingsDataFlow.swift in Sources */,
+				3811DE8825C9D6DD00A708ED /* RequestPermissionsDataFlow.swift in Sources */,
+				3811DE8A25C9D6DD00A708ED /* RequestPermissionsRootView.swift in Sources */,
+				3811DE2525C9D48300A708ED /* MainRootView.swift in Sources */,
+				3811DE8C25C9D6DD00A708ED /* RequestPermissionsViewModel.swift in Sources */,
+				3811DE3125C9D49500A708ED /* HomeProvider.swift in Sources */,
 				388E5A5C25B6F0770019842D /* JSON.swift in Sources */,
+				3811DEB625C9D88300A708ED /* UnlockManager.swift in Sources */,
+				3811DE1825C9D40400A708ED /* Router.swift in Sources */,
+				3811DEAF25C9D88300A708ED /* KeyValueStorage.swift in Sources */,
+				3811DE4E25C9D4B800A708ED /* AuthotizedRootRootView.swift in Sources */,
+				3811DE7D25C9D6D300A708ED /* LoginRootView.swift in Sources */,
+				3811DE4025C9D4A100A708ED /* SettingsBuilder.swift in Sources */,
+				3811DE0B25C9D32F00A708ED /* BaseView.swift in Sources */,
+				3811DE3225C9D49500A708ED /* HomeDataFlow.swift in Sources */,
 				384E803425C385E60086DB71 /* JavaScriptWorker.swift in Sources */,
+				3811DE7A25C9D6D300A708ED /* LoginDataFlow.swift in Sources */,
+				3811DE5D25C9D4D500A708ED /* Publisher.swift in Sources */,
+				3811DEAE25C9D88300A708ED /* Cache.swift in Sources */,
+				3811DEAD25C9D88300A708ED /* UserDefaults+Cache.swift in Sources */,
+				3811DE5A25C9D4D500A708ED /* CheckBox.swift in Sources */,
+				3811DE2225C9D48300A708ED /* MainProvider.swift in Sources */,
+				3811DE0C25C9D32F00A708ED /* BaseProvider.swift in Sources */,
+				3811DE5C25C9D4D500A708ED /* Formatters.swift in Sources */,
+				3811DEAA25C9D88300A708ED /* RemoteService.swift in Sources */,
+				3811DEC525C9D99900A708ED /* StorageContainer.swift in Sources */,
+				3811DE7F25C9D6D300A708ED /* LoginBuilder.swift in Sources */,
+				3811DE3525C9D49500A708ED /* HomeRootView.swift in Sources */,
+				3811DE5E25C9D4D500A708ED /* FlowStack.swift in Sources */,
+				3811DEC325C9D99900A708ED /* UIContainer.swift in Sources */,
+				3811DE6125C9D4D500A708ED /* ViewModifiers.swift in Sources */,
+				3811DEAC25C9D88300A708ED /* NetworkManager.swift in Sources */,
+				3811DE3325C9D49500A708ED /* HomeBuilder.swift in Sources */,
+				3811DEA925C9D88300A708ED /* AppearanceManager.swift in Sources */,
+				3811DE2125C9D48300A708ED /* MainBuilder.swift in Sources */,
+				3811DEB325C9D88300A708ED /* ImageFileStorage.swift in Sources */,
+				3811DE7925C9D6D300A708ED /* LoginViewModel.swift in Sources */,
+				3811DEAB25C9D88300A708ED /* HTTPResponseStatus.swift in Sources */,
+				3811DE5F25C9D4D500A708ED /* ProgressBar.swift in Sources */,
+				3811DE4F25C9D4B800A708ED /* AuthotizedRootDataFlow.swift in Sources */,
+				3811DE6025C9D4D500A708ED /* Injected.swift in Sources */,
+				3811DE5025C9D4B800A708ED /* AuthotizedRootProvider.swift in Sources */,
+				3811DE6C25C9D62600A708ED /* OnboardingDataFlow.swift in Sources */,
+				3811DE2425C9D48300A708ED /* MainViewModel.swift in Sources */,
+				3811DE3F25C9D4A100A708ED /* SettingsViewModel.swift in Sources */,
+				3811DE5B25C9D4D500A708ED /* Persisted.swift in Sources */,
+				3811DEB725C9D88300A708ED /* AuthorizationManager.swift in Sources */,
+				3811DEB025C9D88300A708ED /* BaseKeychain.swift in Sources */,
+				3811DE4D25C9D4B800A708ED /* AuthotizedRootViewModel.swift in Sources */,
+				3811DE6A25C9D62600A708ED /* OnboardingBuilder.swift in Sources */,
+				3811DEC425C9D99900A708ED /* NetworkContainer.swift in Sources */,
+				3811DE4325C9D4A100A708ED /* SettingsProvider.swift in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -323,11 +849,12 @@
 			buildSettings = {
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
 				ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
+				CODE_SIGN_ENTITLEMENTS = FreeAPS/Resources/FreeAPS.entitlements;
 				CODE_SIGN_STYLE = Automatic;
 				DEVELOPMENT_ASSET_PATHS = "\"FreeAPS/Preview Content\"";
 				DEVELOPMENT_TEAM = BA7ZHP4963;
 				ENABLE_PREVIEWS = YES;
-				INFOPLIST_FILE = FreeAPS/Info.plist;
+				INFOPLIST_FILE = FreeAPS/Resources/Info.plist;
 				IPHONEOS_DEPLOYMENT_TARGET = 14.0;
 				LD_RUNPATH_SEARCH_PATHS = (
 					"$(inherited)",
@@ -345,11 +872,12 @@
 			buildSettings = {
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
 				ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
+				CODE_SIGN_ENTITLEMENTS = FreeAPS/Resources/FreeAPS.entitlements;
 				CODE_SIGN_STYLE = Automatic;
 				DEVELOPMENT_ASSET_PATHS = "\"FreeAPS/Preview Content\"";
 				DEVELOPMENT_TEAM = BA7ZHP4963;
 				ENABLE_PREVIEWS = YES;
-				INFOPLIST_FILE = FreeAPS/Info.plist;
+				INFOPLIST_FILE = FreeAPS/Resources/Info.plist;
 				IPHONEOS_DEPLOYMENT_TARGET = 14.0;
 				LD_RUNPATH_SEARCH_PATHS = (
 					"$(inherited)",
@@ -384,6 +912,38 @@
 			defaultConfigurationName = Release;
 		};
 /* End XCConfigurationList section */
+
+/* Begin XCRemoteSwiftPackageReference section */
+		3811DE0E25C9D37700A708ED /* XCRemoteSwiftPackageReference "Swinject" */ = {
+			isa = XCRemoteSwiftPackageReference;
+			repositoryURL = "https://github.com/Swinject/Swinject";
+			requirement = {
+				kind = upToNextMajorVersion;
+				minimumVersion = 2.7.1;
+			};
+		};
+		3811DEB925C9D8DD00A708ED /* XCRemoteSwiftPackageReference "Moya" */ = {
+			isa = XCRemoteSwiftPackageReference;
+			repositoryURL = "https://github.com/Moya/Moya";
+			requirement = {
+				kind = upToNextMajorVersion;
+				minimumVersion = 14.0.0;
+			};
+		};
+/* End XCRemoteSwiftPackageReference section */
+
+/* Begin XCSwiftPackageProductDependency section */
+		3811DE0F25C9D37700A708ED /* Swinject */ = {
+			isa = XCSwiftPackageProductDependency;
+			package = 3811DE0E25C9D37700A708ED /* XCRemoteSwiftPackageReference "Swinject" */;
+			productName = Swinject;
+		};
+		3811DEBA25C9D8DD00A708ED /* Moya */ = {
+			isa = XCSwiftPackageProductDependency;
+			package = 3811DEB925C9D8DD00A708ED /* XCRemoteSwiftPackageReference "Moya" */;
+			productName = Moya;
+		};
+/* End XCSwiftPackageProductDependency section */
 	};
 	rootObject = 388E595025AD948C0019842D /* Project object */;
 }

+ 52 - 0
FreeAPS.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved

@@ -0,0 +1,52 @@
+{
+  "object": {
+    "pins": [
+      {
+        "package": "Alamofire",
+        "repositoryURL": "https://github.com/Alamofire/Alamofire.git",
+        "state": {
+          "branch": null,
+          "revision": "eaf6e622dd41b07b251d8f01752eab31bc811493",
+          "version": "5.4.1"
+        }
+      },
+      {
+        "package": "Moya",
+        "repositoryURL": "https://github.com/Moya/Moya",
+        "state": {
+          "branch": null,
+          "revision": "b3e5a233e0d85fd4d69f561c80988590859c7dee",
+          "version": "14.0.0"
+        }
+      },
+      {
+        "package": "ReactiveSwift",
+        "repositoryURL": "https://github.com/Moya/ReactiveSwift.git",
+        "state": {
+          "branch": null,
+          "revision": "f195d82bb30e412e70446e2b4a77e1b514099e88",
+          "version": "6.1.0"
+        }
+      },
+      {
+        "package": "RxSwift",
+        "repositoryURL": "https://github.com/ReactiveX/RxSwift.git",
+        "state": {
+          "branch": null,
+          "revision": "002d325b0bdee94e7882e1114af5ff4fe1e96afa",
+          "version": "5.1.1"
+        }
+      },
+      {
+        "package": "Swinject",
+        "repositoryURL": "https://github.com/Swinject/Swinject",
+        "state": {
+          "branch": null,
+          "revision": "8a76d2c74bafbb455763487cc6a08e91bad1f78b",
+          "version": "2.7.1"
+        }
+      }
+    ]
+  },
+  "version": 1
+}

+ 84 - 0
FreeAPS.xcodeproj/xcuserdata/i.valkou.xcuserdatad/xcschemes/xcschememanagement.plist

@@ -9,6 +9,90 @@
 			<key>orderHint</key>
 			<integer>0</integer>
 		</dict>
+		<key>ReactiveSwift (Playground) 1.xcscheme</key>
+		<dict>
+			<key>isShown</key>
+			<false/>
+			<key>orderHint</key>
+			<integer>8</integer>
+		</dict>
+		<key>ReactiveSwift (Playground) 2.xcscheme</key>
+		<dict>
+			<key>isShown</key>
+			<false/>
+			<key>orderHint</key>
+			<integer>9</integer>
+		</dict>
+		<key>ReactiveSwift (Playground).xcscheme</key>
+		<dict>
+			<key>isShown</key>
+			<false/>
+			<key>orderHint</key>
+			<integer>7</integer>
+		</dict>
+		<key>ReactiveSwift-UIExamples (Playground) 1.xcscheme</key>
+		<dict>
+			<key>isShown</key>
+			<false/>
+			<key>orderHint</key>
+			<integer>5</integer>
+		</dict>
+		<key>ReactiveSwift-UIExamples (Playground) 2.xcscheme</key>
+		<dict>
+			<key>isShown</key>
+			<false/>
+			<key>orderHint</key>
+			<integer>6</integer>
+		</dict>
+		<key>ReactiveSwift-UIExamples (Playground).xcscheme</key>
+		<dict>
+			<key>isShown</key>
+			<false/>
+			<key>orderHint</key>
+			<integer>4</integer>
+		</dict>
+		<key>Rx (Playground) 1.xcscheme</key>
+		<dict>
+			<key>isShown</key>
+			<false/>
+			<key>orderHint</key>
+			<integer>11</integer>
+		</dict>
+		<key>Rx (Playground) 2.xcscheme</key>
+		<dict>
+			<key>isShown</key>
+			<false/>
+			<key>orderHint</key>
+			<integer>12</integer>
+		</dict>
+		<key>Rx (Playground).xcscheme</key>
+		<dict>
+			<key>isShown</key>
+			<false/>
+			<key>orderHint</key>
+			<integer>10</integer>
+		</dict>
+		<key>Sample-iOS (Playground) 1.xcscheme</key>
+		<dict>
+			<key>isShown</key>
+			<false/>
+			<key>orderHint</key>
+			<integer>2</integer>
+		</dict>
+		<key>Sample-iOS (Playground) 2.xcscheme</key>
+		<dict>
+			<key>isShown</key>
+			<false/>
+			<key>orderHint</key>
+			<integer>3</integer>
+		</dict>
+		<key>Sample-iOS (Playground).xcscheme</key>
+		<dict>
+			<key>isShown</key>
+			<false/>
+			<key>orderHint</key>
+			<integer>0</integer>
+		</dict>
 	</dict>
 </dict>
 </plist>

+ 0 - 24
FreeAPS/ContentView.swift

@@ -1,24 +0,0 @@
-//
-//  ContentView.swift
-//  FreeAPS
-//
-//  Created by Ivan Valkou on 12.01.2021.
-//
-
-import SwiftUI
-
-struct ContentView: View {
-    var body: some View {
-        Text("Hello, world!")
-            .padding()
-            .onAppear {
-                OpenAPS().test()
-            }
-    }
-}
-
-struct ContentView_Previews: PreviewProvider {
-    static var previews: some View {
-        ContentView()
-    }
-}

+ 0 - 17
FreeAPS/FreeAPSApp.swift

@@ -1,17 +0,0 @@
-//
-//  FreeAPSApp.swift
-//  FreeAPS
-//
-//  Created by Ivan Valkou on 12.01.2021.
-//
-
-import SwiftUI
-
-@main
-struct FreeAPSApp: App {
-    var body: some Scene {
-        WindowGroup {
-            ContentView()
-        }
-    }
-}

FreeAPS/Assets.xcassets/AccentColor.colorset/Contents.json → FreeAPS/Resources/Assets.xcassets/AccentColor.colorset/Contents.json


FreeAPS/Assets.xcassets/AppIcon.appiconset/Contents.json → FreeAPS/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json


FreeAPS/Assets.xcassets/Contents.json → FreeAPS/Resources/Assets.xcassets/Contents.json


+ 10 - 0
FreeAPS/Resources/FreeAPS.entitlements

@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>com.apple.developer.applesignin</key>
+	<array>
+		<string>Default</string>
+	</array>
+</dict>
+</plist>

FreeAPS/Info.plist → FreeAPS/Resources/Info.plist


javascript/autosens-bundle.js → FreeAPS/Resources/javascript/autosens-bundle.js


javascript/basal-set-temp-bundle.js → FreeAPS/Resources/javascript/basal-set-temp-bundle.js


javascript/determine-basal-bundle.js → FreeAPS/Resources/javascript/determine-basal-bundle.js


javascript/glucose-get-last-bundle.js → FreeAPS/Resources/javascript/glucose-get-last-bundle.js


javascript/iob-bundle.js → FreeAPS/Resources/javascript/iob-bundle.js


javascript/meal-bundle.js → FreeAPS/Resources/javascript/meal-bundle.js


javascript/prepare-autosens.js → FreeAPS/Resources/javascript/prepare-autosens.js


javascript/prepare-determine-basal.js → FreeAPS/Resources/javascript/prepare-determine-basal.js


javascript/prepare-iob.js → FreeAPS/Resources/javascript/prepare-iob.js


javascript/prepare-meal.js → FreeAPS/Resources/javascript/prepare-meal.js


json/autosens.json → FreeAPS/Resources/json/autosens.json


json/basal_profile.json → FreeAPS/Resources/json/basal_profile.json


json/carbhistory.json → FreeAPS/Resources/json/carbhistory.json


json/clock.json → FreeAPS/Resources/json/clock.json


json/entries.json → FreeAPS/Resources/json/entries.json


json/example.sh → FreeAPS/Resources/json/example.sh


json/glucose.json → FreeAPS/Resources/json/glucose.json


json/iob.json → FreeAPS/Resources/json/iob.json


json/meal.json → FreeAPS/Resources/json/meal.json


json/profile.json → FreeAPS/Resources/json/profile.json


json/pumphistory.json → FreeAPS/Resources/json/pumphistory.json


json/suggested.json → FreeAPS/Resources/json/suggested.json


json/temp_basal.json → FreeAPS/Resources/json/temp_basal.json


+ 26 - 0
FreeAPS/Sources/Application/FreeAPSApp.swift

@@ -0,0 +1,26 @@
+//
+//  FreeAPSApp.swift
+//  FreeAPS
+//
+//  Created by Ivan Valkou on 12.01.2021.
+//
+
+import SwiftUI
+import Swinject
+
+@main
+struct FreeAPSApp: App {
+    private let resolver = Container(defaultObjectScope: .container) { container in
+        UIContainer.register(container: container)
+        StorageContainer.register(container: container)
+        NetworkContainer.register(container: container)
+        SecurityContainer.register(container: container)
+    }.synchronize()
+
+    var body: some Scene {
+        resolver.resolve(AppearanceManager.self)!.setupGlobalAppearance()
+        return WindowGroup {
+            Main.Builder(resolver: resolver).buildView()
+        }
+    }
+}

+ 9 - 0
FreeAPS/Sources/Containers/NetworkContainer.swift

@@ -0,0 +1,9 @@
+import Swinject
+import UIKit
+
+enum NetworkContainer {
+    static func register(container: Container) {
+        container.register(NetworkManager.self) { _ in BaseNetworkManager() }
+        container.register(AuthorizationManager.self) { r in BaseAuthorizationManager(resolver: r) }
+    }
+}

+ 7 - 0
FreeAPS/Sources/Containers/SecurityContainer.swift

@@ -0,0 +1,7 @@
+import Swinject
+
+enum SecurityContainer {
+    static func register(container: Container) {
+        container.register(UnlockManager.self) { _ in BaseUnlockManager() }
+    }
+}

+ 23 - 0
FreeAPS/Sources/Containers/StorageContainer.swift

@@ -0,0 +1,23 @@
+import Foundation
+import Swinject
+
+enum StorageContainer {
+    static func register(container: Container) {
+        container.register(FileManager.self) { _ in
+            Foundation.FileManager.default
+        }
+
+        container.register(Keychain.self) { _ in BaseKeychain() }
+
+        container.register(IsDrinkImageFileStorage.self) { r in BaseImageFileStorage(resolver: r, name: "IsDrink")
+        }
+        container
+            .register(DrinkTypeImageFileStorage.self) { r in BaseImageFileStorage(resolver: r, name: "DrinkType")
+            }
+    }
+}
+
+protocol IsDrinkImageFileStorage: ImageFileStorage {}
+protocol DrinkTypeImageFileStorage: ImageFileStorage {}
+extension BaseImageFileStorage: IsDrinkImageFileStorage {}
+extension BaseImageFileStorage: DrinkTypeImageFileStorage {}

+ 8 - 0
FreeAPS/Sources/Containers/UIContainer.swift

@@ -0,0 +1,8 @@
+import Swinject
+
+enum UIContainer {
+    static func register(container: Container) {
+        container.register(AppearanceManager.self) { _ in BaseAppearanceManager() }
+        container.register(Router.self) { r in BaseRouter(resolver: r) }
+    }
+}

+ 9 - 0
FreeAPS/Sources/Helpers/CheckBox.swift

@@ -0,0 +1,9 @@
+import SwiftUI
+
+struct CheckBox: View {
+    @Binding var isChecked: Bool
+
+    var body: some View {
+        Image(systemName: isChecked ? "checkmark.circle.fill" : "circle")
+    }
+}

+ 82 - 0
FreeAPS/Sources/Helpers/FlowStack.swift

@@ -0,0 +1,82 @@
+import SwiftUI
+
+@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
+struct FlowStack<Content>: View where Content: View {
+    // The number of columns we want to display
+    var columns: Int
+    // The total number of items in the stack
+    var numItems: Int
+    // The alignment of our columns in the last row
+    // when they don't fill all the column slots
+    var alignment: HorizontalAlignment
+
+    let content: (Int, CGFloat) -> Content
+
+    private func width(for size: CGSize) -> CGFloat {
+        size.width / CGFloat(columns)
+    }
+
+    private func index(forRow row: Int, column: Int) -> Int {
+        (row * columns) + column
+    }
+
+    private var lastRowColumns: Int { numItems % columns }
+
+    private var rows: Int { numItems / columns }
+
+    init(
+        columns: Int,
+        numItems: Int,
+        alignment: HorizontalAlignment?,
+        @ViewBuilder content: @escaping (Int, CGFloat) -> Content
+    ) {
+        self.content = content
+        self.columns = columns
+        self.numItems = numItems
+        self.alignment = alignment ?? HorizontalAlignment.leading
+    }
+
+    var body: some View {
+        // A GeometryReader is required to size items in the scroll view
+        GeometryReader { geometry in
+
+            // Assume a vertical scrolling orientation for the grid
+            ScrollView(Axis.Set.vertical) {
+                // VStacks are our rows
+                VStack(alignment: self.alignment, spacing: 0) {
+                    ForEach(0 ..< self.rows) { row in
+
+                        // HStacks are our columns
+                        HStack(spacing: 0) {
+                            ForEach(0 ..< self.columns) { column in
+                                self.content(
+                                    // Pass the index to the content
+                                    self.index(forRow: row, column: column),
+                                    // Pass the column width to the content
+                                    self.width(for: geometry.size)
+                                )
+                                // Size the content to frame to fill the column
+                                .frame(width: self.width(for: geometry.size))
+                            }
+                        }
+                    }
+
+                    // Last row
+                    // HStacks are our columns
+                    HStack(spacing: 0) {
+                        ForEach(0 ..< self.lastRowColumns) { column in
+                            self.content(
+                                // Pass the index to the content
+                                self.index(forRow: self.rows, column: column),
+                                // Pass the column width to the content
+                                self.width(for: geometry.size)
+                            )
+                            // Size the content to frame to fill the column
+                            .frame(width: self.width(for: geometry.size))
+                        }
+                    }
+                }
+            }
+        }
+    }
+}

+ 17 - 0
FreeAPS/Sources/Helpers/Formatters.swift

@@ -0,0 +1,17 @@
+import Foundation
+import HealthKit
+
+enum Formatters {
+    static func percent(for number: Double) -> String {
+        let formater = NumberFormatter()
+        formater.numberStyle = .percent
+        return formater.string(for: number)!
+    }
+
+    static func timeFor(minutes: Int) -> String {
+        let formater = DateComponentsFormatter()
+        formater.unitsStyle = .abbreviated
+        formater.allowedUnits = [.hour, .minute]
+        return formater.string(from: TimeInterval(minutes * 60))!
+    }
+}

+ 40 - 0
FreeAPS/Sources/Helpers/Injected.swift

@@ -0,0 +1,40 @@
+import Swinject
+
+protocol Injectable {
+    func injectServices(_ resolver: Resolver)
+}
+
+@propertyWrapper
+final class Injected<Resolve, Service>: Resolvable {
+    var wrappedValue: Service!
+
+    init(as _: Resolve.Type) {}
+
+    func resolve(_ resolver: Resolver) {
+        if wrappedValue == nil {
+            wrappedValue = (resolver.resolve(Resolve.self) as! Service)
+        }
+    }
+}
+
+private protocol Resolvable {
+    func resolve(_: Resolver)
+}
+
+extension Injected where Resolve == Service {
+    convenience init() {
+        self.init(as: Service.self)
+    }
+}
+
+extension Injectable {
+    func injectServices(_ resolver: Resolver) {
+        Mirror(reflecting: self).allChildrenValues.forEach { ($0 as? Resolvable)?.resolve(resolver) }
+    }
+}
+
+private extension Mirror {
+    var allChildrenValues: [Any] {
+        children.map(\.value) + (superclassMirror?.allChildrenValues ?? [])
+    }
+}

FreeAPS/Helpers/JSON.swift → FreeAPS/Sources/Helpers/JSON.swift


+ 19 - 0
FreeAPS/Sources/Helpers/Persisted.swift

@@ -0,0 +1,19 @@
+import Foundation
+
+/// Attention! Do not use this wrapper for mutating structure with `didSet` handler into property owner!
+/// `didSet` will never called if structure mutate into itself (by "mutating functions").
+@propertyWrapper
+struct Persisted<Value: Codable> {
+    var wrappedValue: Value? {
+        set { storage.setValue(newValue, forKey: key) }
+        get { storage.getValue(Value.self, forKey: key) }
+    }
+
+    private let key: String
+    private let storage: KeyValueStorage
+
+    init(key: String, storage: KeyValueStorage = UserDefaults.standard) {
+        self.storage = storage
+        self.key = key
+    }
+}

+ 25 - 0
FreeAPS/Sources/Helpers/ProgressBar.swift

@@ -0,0 +1,25 @@
+import SwiftUI
+
+struct ProgressBar: View {
+    @Binding var value: Float
+
+    var body: some View {
+        GeometryReader { geometry in
+            ZStack(alignment: .leading) {
+                Capsule(style: .circular)
+                    .frame(width: geometry.size.width, height: geometry.size.height)
+                    .opacity(0.3)
+                    .foregroundColor(.secondary)
+
+                Capsule(style: .circular)
+                    .frame(
+                        width: min(CGFloat(self.value) * geometry.size.width, geometry.size.width),
+                        height: geometry.size.height
+                    )
+                    .foregroundColor(.accentColor)
+                    .animation(.linear)
+            }
+        }
+        .frame(height: 20)
+    }
+}

+ 29 - 0
FreeAPS/Sources/Helpers/Publisher.swift

@@ -0,0 +1,29 @@
+import Combine
+
+protocol OptionalType {
+    associatedtype Wrapped
+
+    var optional: Wrapped? { get }
+}
+
+extension Optional: OptionalType {
+    public var optional: Wrapped? { self }
+}
+
+extension Publisher where Output: OptionalType {
+    func ignoreNil() -> AnyPublisher<Output.Wrapped, Failure> {
+        flatMap { output -> AnyPublisher<Output.Wrapped, Failure> in
+            guard let output = output.optional else {
+                return Empty<Output.Wrapped, Failure>(completeImmediately: false).eraseToAnyPublisher()
+            }
+            return Just(output).setFailureType(to: Failure.self).eraseToAnyPublisher()
+        }
+        .eraseToAnyPublisher()
+    }
+}
+
+extension Publisher {
+    func combineWithPrevious() -> AnyPublisher<(Output, Output), Failure> {
+        zip(dropFirst()).eraseToAnyPublisher()
+    }
+}

+ 110 - 0
FreeAPS/Sources/Helpers/ViewModifiers.swift

@@ -0,0 +1,110 @@
+import Combine
+import SwiftUI
+
+struct RoundedBackground: ViewModifier {
+    private let color: Color
+
+    init(color: Color = Color("CapsuleColor")) {
+        self.color = color
+    }
+
+    func body(content: Content) -> some View {
+        content
+            .padding()
+            .background(
+                RoundedRectangle(cornerRadius: 8, style: .continuous)
+                    .fill()
+                    .foregroundColor(color)
+            )
+    }
+}
+
+struct CapsulaBackground: ViewModifier {
+    private let color: Color
+
+    init(color: Color = Color("CapsuleColor")) {
+        self.color = color
+    }
+
+    func body(content: Content) -> some View {
+        content
+            .padding()
+            .background(
+                Capsule()
+                    .fill()
+                    .foregroundColor(color)
+            )
+    }
+}
+
+struct Link<T>: ViewModifier where T: View {
+    private let destination: T
+    init(destination: T) {
+        self.destination = destination
+    }
+
+    func body(content: Content) -> some View {
+        ZStack {
+            NavigationLink(destination: destination) {
+                EmptyView()
+            }.hidden()
+            content
+        }
+    }
+}
+
+struct AdaptsToSoftwareKeyboard: ViewModifier {
+    @State var currentHeight: CGFloat = 0
+
+    func body(content: Content) -> some View {
+        content
+            .padding(.bottom, currentHeight).animation(.easeOut(duration: 0.25))
+            .edgesIgnoringSafeArea(currentHeight == 0 ? Edge.Set() : .bottom)
+            .onAppear(perform: subscribeToKeyboardChanges)
+    }
+
+    private let keyboardHeightOnOpening = NotificationCenter.default
+        .publisher(for: UIResponder.keyboardWillShowNotification)
+        .map { $0.userInfo![UIResponder.keyboardFrameEndUserInfoKey] as! CGRect }
+        .map { $0.height }
+
+    private let keyboardHeightOnHiding = NotificationCenter.default
+        .publisher(for: UIResponder.keyboardWillHideNotification)
+        .map { _ in CGFloat(0) }
+
+    private func subscribeToKeyboardChanges() {
+        _ = Publishers.Merge(keyboardHeightOnOpening, keyboardHeightOnHiding)
+            .subscribe(on: DispatchQueue.main)
+            .sink { height in
+                if self.currentHeight == 0 || height == 0 {
+                    self.currentHeight = height
+                }
+            }
+    }
+}
+
+extension View {
+    func roundedBackground() -> some View {
+        modifier(RoundedBackground())
+    }
+
+    func buttonBackground() -> some View {
+        modifier(RoundedBackground(color: .accentColor))
+    }
+
+    func navigationLink<V: BaseView>(to screen: Screen, from view: V) -> some View {
+        modifier(Link(destination: view.viewModel.view(for: screen)))
+    }
+
+    func adaptsToSoftwareKeyboard() -> some View {
+        modifier(AdaptsToSoftwareKeyboard())
+    }
+
+    func modal<V: BaseView>(for screen: Screen?, from view: V) -> some View {
+        onTapGesture {
+            view.viewModel.showModal(for: screen)
+        }
+    }
+
+    func asAny() -> AnyView { .init(self) }
+}

FreeAPS/Models/Autosens.swift → FreeAPS/Sources/Models/Autosens.swift


FreeAPS/Models/Profile.swift → FreeAPS/Sources/Models/Profile.swift


+ 7 - 0
FreeAPS/Sources/Models/User.swift

@@ -0,0 +1,7 @@
+import Foundation
+
+struct User: Codable {
+    var id: UUID?
+    var name: String
+    var email: String
+}

+ 3 - 0
FreeAPS/Sources/Modules/AuthotizedRoot/AuthotizedRootBuilder.swift

@@ -0,0 +1,3 @@
+extension AuthotizedRoot {
+    final class Builder: BaseModuleBuilder<RootView, ViewModel<Provider>, Provider> {}
+}

+ 18 - 0
FreeAPS/Sources/Modules/AuthotizedRoot/AuthotizedRootDataFlow.swift

@@ -0,0 +1,18 @@
+import SwiftUI
+
+enum AuthotizedRoot {
+    enum Config {
+        static let initialTab = 0
+    }
+
+    struct Tab: Identifiable {
+        let rootScreen: Screen
+        let view: AnyView
+        let image: Image
+        let text: Text
+
+        var id: Int { rootScreen.id }
+    }
+}
+
+protocol AuthotizedRootProvider: Provider {}

+ 5 - 0
FreeAPS/Sources/Modules/AuthotizedRoot/AuthotizedRootProvider.swift

@@ -0,0 +1,5 @@
+extension AuthotizedRoot {
+    final class Provider: BaseProvider, AuthotizedRootProvider {
+        @Injected() var authorizationManager: AuthorizationManager!
+    }
+}

+ 26 - 0
FreeAPS/Sources/Modules/AuthotizedRoot/AuthotizedRootViewModel.swift

@@ -0,0 +1,26 @@
+import SwiftUI
+import Swinject
+
+extension AuthotizedRoot {
+    class ViewModel<Provider>: BaseViewModel<Provider>, ObservableObject where Provider: AuthotizedRootProvider {
+        @Published private(set) var tabs: [Tab] = []
+        @Published var selectedTab = Config.initialTab
+        @Published private(set) var isAuthotized = true
+
+        required init(provider: Provider, resolver: Resolver) {
+            super.init(provider: provider, resolver: resolver)
+            setupTabs()
+        }
+
+        private func setupTabs() {
+            tabs = router.tabs.map { $0.tab(resolver: self.resolver) }
+        }
+
+        override func subscribe() {
+            router.selectTab
+                .receive(on: RunLoop.main)
+                .assign(to: \.selectedTab, on: self)
+                .store(in: &lifetime)
+        }
+    }
+}

+ 24 - 0
FreeAPS/Sources/Modules/AuthotizedRoot/View/AuthotizedRootRootView.swift

@@ -0,0 +1,24 @@
+import SwiftUI
+
+extension AuthotizedRoot {
+    struct RootView: BaseView {
+        @EnvironmentObject var viewModel: ViewModel<Provider>
+
+        var body: some View {
+            TabView(selection: $viewModel.selectedTab) {
+                ForEach(viewModel.tabs) { tab in
+                    NavigationView {
+                        tab.view
+                    }
+                    .tabItem {
+                        VStack {
+                            tab.image
+                            tab.text
+                        }
+                    }
+                    .tag(tab.id)
+                }
+            }
+        }
+    }
+}

+ 25 - 0
FreeAPS/Sources/Modules/Base/BaseModuleBuilder.swift

@@ -0,0 +1,25 @@
+import SwiftUI
+import Swinject
+
+protocol ModuleBuilder {
+    associatedtype View: SwiftUI.View
+    func buildView() -> View
+}
+
+class BaseModuleBuilder<View: BaseView, ViewModel: ObservableObject, Provider: FreeAPS.Provider>: ModuleBuilder
+    where ViewModel: BaseViewModel<Provider>, View.ViewModel == ViewModel {
+    let resolver: Resolver
+    lazy var viewModel: ViewModel = { buildViewModel() }()
+
+    init(resolver: Resolver) {
+        self.resolver = resolver
+    }
+
+    func buildViewModel() -> ViewModel {
+        ViewModel(provider: Provider(resolver: resolver), resolver: resolver)
+    }
+
+    func buildView() -> some SwiftUI.View {
+        View().environmentObject(viewModel)
+    }
+}

+ 29 - 0
FreeAPS/Sources/Modules/Base/BaseProvider.swift

@@ -0,0 +1,29 @@
+import Combine
+import Foundation
+import Swinject
+
+protocol Provider {
+    init(resolver: Resolver)
+    var user: CurrentValueSubject<User?, Never> { get }
+}
+
+class BaseProvider: Provider, Injectable {
+    let user = CurrentValueSubject<User?, Never>(nil)
+    var lifetime = Set<AnyCancellable>()
+    required init(resolver: Resolver) {
+        injectServices(resolver)
+        makeTestUser()
+    }
+}
+
+extension BaseProvider {
+    func makeTestUser() {
+        let user = User(
+            id: UUID(),
+            name: "Vasiliy",
+            email: "example@mail.ru"
+        )
+
+        self.user.send(user)
+    }
+}

+ 11 - 0
FreeAPS/Sources/Modules/Base/BaseView.swift

@@ -0,0 +1,11 @@
+import SwiftUI
+
+protocol BaseView: View {
+    associatedtype ViewModel: FreeAPS.ViewModel
+    var viewModel: ViewModel { get }
+    init()
+}
+
+extension BaseView {
+    init() { self.init() }
+}

+ 38 - 0
FreeAPS/Sources/Modules/Base/BaseViewModel.swift

@@ -0,0 +1,38 @@
+import Combine
+import SwiftUI
+import Swinject
+
+protocol ViewModel {
+    func subscribe()
+    func view(for screen: Screen) -> AnyView
+    func showModal(for screen: Screen?)
+    func hideModal()
+}
+
+class BaseViewModel<Provider>: ViewModel, Injectable where Provider: FreeAPS.Provider {
+    let resolver: Resolver
+    let provider: Provider
+    var lifetime = Set<AnyCancellable>()
+    @Injected() var router: Router!
+
+    required init(provider: Provider, resolver: Resolver) {
+        self.provider = provider
+        self.resolver = resolver
+        injectServices(resolver)
+        subscribe()
+    }
+
+    func subscribe() {}
+
+    func view(for screen: Screen) -> AnyView {
+        router.view(for: screen)
+    }
+
+    func showModal(for screen: Screen?) {
+        router.modalScreen.send(screen)
+    }
+
+    func hideModal() {
+        router.modalScreen.send(nil)
+    }
+}

+ 3 - 0
FreeAPS/Sources/Modules/Home/HomeBuilder.swift

@@ -0,0 +1,3 @@
+extension Home {
+    final class Builder: BaseModuleBuilder<RootView, ViewModel<Provider>, Provider> {}
+}

+ 5 - 0
FreeAPS/Sources/Modules/Home/HomeDataFlow.swift

@@ -0,0 +1,5 @@
+enum Home {
+    enum Config {}
+}
+
+protocol HomeProvider: Provider {}

+ 3 - 0
FreeAPS/Sources/Modules/Home/HomeProvider.swift

@@ -0,0 +1,3 @@
+extension Home {
+    final class Provider: BaseProvider, HomeProvider {}
+}

+ 9 - 0
FreeAPS/Sources/Modules/Home/HomeViewModel.swift

@@ -0,0 +1,9 @@
+import SwiftUI
+
+extension Home {
+    class ViewModel<Provider>: BaseViewModel<Provider>, ObservableObject where Provider: HomeProvider {
+        func runOpenAPS() {
+            OpenAPS().test()
+        }
+    }
+}

+ 22 - 0
FreeAPS/Sources/Modules/Home/View/HomeRootView.swift

@@ -0,0 +1,22 @@
+import SwiftUI
+
+extension Home {
+    struct RootView: BaseView {
+        @EnvironmentObject var viewModel: ViewModel<Provider>
+
+        var body: some View {
+            VStack {
+                Spacer()
+                Button(action: viewModel.runOpenAPS) {
+                    Text("Run test")
+                        .frame(maxWidth: .infinity)
+                        .foregroundColor(.white)
+                        .buttonBackground()
+                }
+                Spacer()
+            }
+            .padding()
+            .navigationBarTitle("Home")
+        }
+    }
+}

+ 3 - 0
FreeAPS/Sources/Modules/Login/LoginBuilder.swift

@@ -0,0 +1,3 @@
+extension Login {
+    final class Builder: BaseModuleBuilder<RootView, ViewModel<Provider>, Provider> {}
+}

+ 36 - 0
FreeAPS/Sources/Modules/Login/LoginDataFlow.swift

@@ -0,0 +1,36 @@
+import AuthenticationServices
+
+enum Login {
+    enum Config {
+        static let credentialsKey = "FreeAPS.Credentials"
+    }
+}
+
+protocol LoginProvider: Provider {
+    func authorize(credentials: ASAuthorizationAppleIDCredential)
+    var credentials: ASAuthorizationAppleIDCredential? { get }
+}
+
+struct CredentialsWrapper: Codable {
+    enum CodingKeys: String, CodingKey {
+        case credentials
+    }
+
+    var credentials: ASAuthorizationAppleIDCredential
+
+    init(_ credentials: ASAuthorizationAppleIDCredential) {
+        self.credentials = credentials
+    }
+
+    func encode(to encoder: Encoder) throws {
+        var container = encoder.container(keyedBy: CodingKeys.self)
+        let data = try NSKeyedArchiver.archivedData(withRootObject: credentials, requiringSecureCoding: true)
+        try container.encode(data, forKey: .credentials)
+    }
+
+    init(from decoder: Decoder) throws {
+        let container = try decoder.container(keyedBy: CodingKeys.self)
+        let data = try container.decode(Data.self, forKey: .credentials)
+        credentials = try (NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as? ASAuthorizationAppleIDCredential)!
+    }
+}

+ 20 - 0
FreeAPS/Sources/Modules/Login/LoginProvider.swift

@@ -0,0 +1,20 @@
+import AuthenticationServices
+
+extension Login {
+    final class Provider: BaseProvider, LoginProvider {
+        @Injected() var authorizationManager: AuthorizationManager!
+        @Injected() private var keychain: Keychain!
+
+        func authorize(credentials: ASAuthorizationAppleIDCredential) {
+            authorizationManager.authorize(credentials: credentials)
+                .sink { _ in
+                    self.keychain.setValue(CredentialsWrapper(credentials), forKey: Config.credentialsKey)
+                }
+                .store(in: &lifetime)
+        }
+
+        var credentials: ASAuthorizationAppleIDCredential? {
+            keychain.getValue(CredentialsWrapper.self, forKey: Config.credentialsKey)?.credentials
+        }
+    }
+}

+ 17 - 0
FreeAPS/Sources/Modules/Login/LoginViewModel.swift

@@ -0,0 +1,17 @@
+import AuthenticationServices
+import SwiftUI
+
+extension Login {
+    class ViewModel<Provider>: BaseViewModel<Provider>, ObservableObject where Provider: LoginProvider {
+        @Published var credentials: ASAuthorizationAppleIDCredential?
+
+        override func subscribe() {
+            credentials = provider.credentials
+
+            $credentials
+                .compactMap { $0 }
+                .sink { self.provider.authorize(credentials: $0) }
+                .store(in: &lifetime)
+        }
+    }
+}

+ 30 - 0
FreeAPS/Sources/Modules/Login/View/LoginRootView.swift

@@ -0,0 +1,30 @@
+import SwiftUI
+import AuthenticationServices
+
+extension Login {
+    struct RootView: BaseView {
+        @EnvironmentObject var viewModel: ViewModel<Provider>
+
+        var body: some View {
+            VStack {
+                Text("FreeAPS").font(.largeTitle)
+                Spacer()
+                SignInWithAppleButton(.signIn) { request in
+                    request.requestedScopes = [.fullName, .email]
+                }
+                onCompletion: { result in
+                    switch result {
+
+                    case let .success(authorisation):
+                        viewModel.credentials = authorisation.credential as? ASAuthorizationAppleIDCredential
+                    case .failure:
+                        viewModel.credentials = nil
+                    }
+                }
+                .frame(width: 300, height: 50)
+                .signInWithAppleButtonStyle(.whiteOutline)
+                Spacer()
+            }.padding()
+        }
+    }
+}

+ 5 - 0
FreeAPS/Sources/Modules/Main/MainBuilder.swift

@@ -0,0 +1,5 @@
+import Swinject
+
+extension Main {
+    final class Builder: BaseModuleBuilder<RootView, ViewModel<Provider>, Provider> {}
+}

+ 30 - 0
FreeAPS/Sources/Modules/Main/MainDataFlow.swift

@@ -0,0 +1,30 @@
+import SwiftUI
+
+enum Main {
+    enum Config {}
+
+    struct Modal: Identifiable {
+        let screen: Screen
+        let view: AnyView
+
+        var id: Int { screen.id }
+    }
+
+    enum Scene {
+        case authorized
+        case onboarding
+
+        var screen: Screen {
+            switch self {
+            case .authorized:
+                return .authorizedRoot
+            case .onboarding:
+                return .onboarding
+            }
+        }
+    }
+}
+
+protocol MainProvider: Provider {
+    var authorizationManager: AuthorizationManager! { get }
+}

+ 7 - 0
FreeAPS/Sources/Modules/Main/MainProvider.swift

@@ -0,0 +1,7 @@
+import Combine
+
+extension Main {
+    final class Provider: BaseProvider, MainProvider {
+        @Injected() var authorizationManager: AuthorizationManager!
+    }
+}

+ 49 - 0
FreeAPS/Sources/Modules/Main/MainViewModel.swift

@@ -0,0 +1,49 @@
+import Combine
+import SwiftUI
+import Swinject
+
+extension Main {
+    class ViewModel<Provider>: BaseViewModel<Provider>, ObservableObject where Provider: MainProvider {
+        @Published private(set) var isAuthotized = false
+        private(set) var modal: Modal?
+        @Published var isModalPresented = false
+        @Published private(set) var scene: Scene!
+        private var nextModal: Modal?
+
+        required init(provider: Provider, resolver: Resolver) {
+            super.init(provider: provider, resolver: resolver)
+            scene = isAuthotized ? .authorized : .onboarding
+        }
+
+        override func subscribe() {
+            router.modalScreen
+                .map { $0?.modal(resolver: self.resolver) }
+                .removeDuplicates { $0?.id == $1?.id }
+                .receive(on: RunLoop.main)
+                .sink { modal in
+                    self.modal = modal
+                    self.isModalPresented = modal != nil
+                }
+                .store(in: &lifetime)
+
+            provider.authorizationManager
+                .authorizationPublisher
+                .receive(on: RunLoop.main)
+                .assign(to: \.isAuthotized, on: self)
+                .store(in: &lifetime)
+
+            $isAuthotized
+                .sink { isAuthotized in
+                    self.scene = isAuthotized ? .authorized : .onboarding
+                }
+                .store(in: &lifetime)
+
+            $isModalPresented
+                .filter { !$0 }
+                .sink { _ in
+                    self.router.modalScreen.send(nil)
+                }
+                .store(in: &lifetime)
+        }
+    }
+}

+ 14 - 0
FreeAPS/Sources/Modules/Main/View/MainRootView.swift

@@ -0,0 +1,14 @@
+import SwiftUI
+
+extension Main {
+    struct RootView: BaseView {
+        @EnvironmentObject var viewModel: ViewModel<Provider>
+
+        var body: some View {
+            viewModel.view(for: viewModel.scene.screen)
+                .sheet(isPresented: $viewModel.isModalPresented) {
+                    NavigationView { self.viewModel.modal!.view }
+                }
+        }
+    }
+}

+ 3 - 0
FreeAPS/Sources/Modules/Onboarding/OnboardingBuilder.swift

@@ -0,0 +1,3 @@
+extension Onboarding {
+    final class Builder: BaseModuleBuilder<RootView, ViewModel<Provider>, Provider> {}
+}

+ 19 - 0
FreeAPS/Sources/Modules/Onboarding/OnboardingDataFlow.swift

@@ -0,0 +1,19 @@
+enum Onboarding {
+    enum Config {}
+
+    enum Stage {
+        case login
+        case requestPermissions
+
+        var screen: Screen {
+            switch self {
+            case .login:
+                return .login
+            case .requestPermissions:
+                return .requestPermissions
+            }
+        }
+    }
+}
+
+protocol OnboardingProvider: Provider {}

+ 3 - 0
FreeAPS/Sources/Modules/Onboarding/OnboardingProvider.swift

@@ -0,0 +1,3 @@
+extension Onboarding {
+    final class Provider: BaseProvider, OnboardingProvider {}
+}

+ 13 - 0
FreeAPS/Sources/Modules/Onboarding/OnboardingViewModel.swift

@@ -0,0 +1,13 @@
+import SwiftUI
+import Swinject
+
+extension Onboarding {
+    class ViewModel<Provider>: BaseViewModel<Provider>, ObservableObject where Provider: OnboardingProvider {
+        @Published var stage: Stage
+
+        required init(provider: Provider, resolver: Resolver) {
+            stage = .login
+            super.init(provider: provider, resolver: resolver)
+        }
+    }
+}

+ 11 - 0
FreeAPS/Sources/Modules/Onboarding/View/OnboardingRootView.swift

@@ -0,0 +1,11 @@
+import SwiftUI
+
+extension Onboarding {
+    struct RootView: BaseView {
+        @EnvironmentObject var viewModel: ViewModel<Provider>
+
+        var body: some View {
+            viewModel.view(for: viewModel.stage.screen)
+        }
+    }
+}

+ 3 - 0
FreeAPS/Sources/Modules/RequestPermissions/RequestPermissionsBuilder.swift

@@ -0,0 +1,3 @@
+extension RequestPermissions {
+    final class Builder: BaseModuleBuilder<RootView, ViewModel<Provider>, Provider> {}
+}

+ 5 - 0
FreeAPS/Sources/Modules/RequestPermissions/RequestPermissionsDataFlow.swift

@@ -0,0 +1,5 @@
+enum RequestPermissions {
+    enum Config {}
+}
+
+protocol RequestPermissionsProvider: Provider {}

+ 3 - 0
FreeAPS/Sources/Modules/RequestPermissions/RequestPermissionsProvider.swift

@@ -0,0 +1,3 @@
+extension RequestPermissions {
+    final class Provider: BaseProvider, RequestPermissionsProvider {}
+}

+ 5 - 0
FreeAPS/Sources/Modules/RequestPermissions/RequestPermissionsViewModel.swift

@@ -0,0 +1,5 @@
+import SwiftUI
+
+extension RequestPermissions {
+    class ViewModel<Provider>: BaseViewModel<Provider>, ObservableObject where Provider: RequestPermissionsProvider {}
+}

+ 13 - 0
FreeAPS/Sources/Modules/RequestPermissions/View/RequestPermissionsRootView.swift

@@ -0,0 +1,13 @@
+import SwiftUI
+
+extension RequestPermissions {
+    struct RootView: BaseView {
+        @EnvironmentObject var viewModel: ViewModel<Provider>
+
+        var body: some View {
+            Text("RequestPermissions screen")
+                .navigationBarTitle("RequestPermissions")
+                .navigationBarItems(leading: Button("Close", action: viewModel.hideModal))
+        }
+    }
+}

+ 3 - 0
FreeAPS/Sources/Modules/Settings/SettingsBuilder.swift

@@ -0,0 +1,3 @@
+extension Settings {
+    final class Builder: BaseModuleBuilder<RootView, ViewModel<Provider>, Provider> {}
+}

+ 5 - 0
FreeAPS/Sources/Modules/Settings/SettingsDataFlow.swift

@@ -0,0 +1,5 @@
+enum Settings {
+    enum Config {}
+}
+
+protocol SettingsProvider: Provider {}

+ 9 - 0
FreeAPS/Sources/Modules/Settings/SettingsProvider.swift

@@ -0,0 +1,9 @@
+extension Settings {
+    final class Provider: BaseProvider, SettingsProvider {
+        @Injected() var authorizationManager: AuthorizationManager!
+
+        func logout() {
+            authorizationManager.logout()
+        }
+    }
+}

+ 5 - 0
FreeAPS/Sources/Modules/Settings/SettingsViewModel.swift

@@ -0,0 +1,5 @@
+import SwiftUI
+
+extension Settings {
+    class ViewModel<Provider>: BaseViewModel<Provider>, ObservableObject where Provider: SettingsProvider {}
+}

+ 16 - 0
FreeAPS/Sources/Modules/Settings/View/SettingsRootView.swift

@@ -0,0 +1,16 @@
+import SwiftUI
+
+extension Settings {
+    struct RootView: BaseView {
+        @EnvironmentObject var viewModel: ViewModel<Provider>
+
+        var body: some View {
+            VStack {
+                Text("Settings screen")
+                Spacer()
+            }
+            .padding()
+            .navigationBarTitle("Settings")
+        }
+    }
+}

FreeAPS/OpenAPS/JavaScriptWorker.swift → FreeAPS/Sources/OpenAPS/JavaScriptWorker.swift


FreeAPS/OpenAPS/OpenAPS.swift → FreeAPS/Sources/OpenAPS/OpenAPS.swift


FreeAPS/OpenAPS/Script.swift → FreeAPS/Sources/OpenAPS/Script.swift


+ 36 - 0
FreeAPS/Sources/Router/Router.swift

@@ -0,0 +1,36 @@
+import Combine
+import SwiftUI
+import Swinject
+
+protocol Router {
+    var selectTab: PassthroughSubject<Int, Never> { get }
+    var modalScreen: CurrentValueSubject<Screen?, Never> { get }
+    var tabs: [Screen] { get }
+    func view(for screen: Screen) -> AnyView
+}
+
+final class BaseRouter: Router {
+    let selectTab = PassthroughSubject<Int, Never>()
+    let modalScreen = CurrentValueSubject<Screen?, Never>(nil)
+
+    private let resolver: Resolver
+
+    let tabs: [Screen] = [
+        .home,
+        .settings
+    ]
+
+    private var screens: [Screen.ID: AnyView] = [:]
+
+    init(resolver: Resolver) {
+        self.resolver = resolver
+    }
+
+    func view(for screen: Screen) -> AnyView {
+        if let view = screens[screen.id] {
+            return view
+        }
+        screens[screen.id] = screen.view(resolver: resolver).asAny()
+        return screens[screen.id]!
+    }
+}

+ 58 - 0
FreeAPS/Sources/Router/Screen.swift

@@ -0,0 +1,58 @@
+import SwiftUI
+import Swinject
+
+enum Screen: Identifiable {
+    case home
+    case settings
+    case onboarding
+    case authorizedRoot
+    case login
+    case requestPermissions
+
+    var id: Int { String(reflecting: self).hashValue }
+}
+
+extension Screen {
+    func view(resolver: Resolver) -> AnyView {
+        switch self {
+        case .home:
+            return Home.Builder(resolver: resolver).buildView().asAny()
+        case .settings:
+            return Settings.Builder(resolver: resolver).buildView().asAny()
+        case .onboarding:
+            return Onboarding.Builder(resolver: resolver).buildView().asAny()
+        case .authorizedRoot:
+            return AuthotizedRoot.Builder(resolver: resolver).buildView().asAny()
+        case .login:
+            return Login.Builder(resolver: resolver).buildView().asAny()
+        case .requestPermissions:
+            return RequestPermissions.Builder(resolver: resolver).buildView().asAny()
+        }
+    }
+
+    func tab(resolver: Resolver) -> AuthotizedRoot.Tab {
+        let tabView = view(resolver: resolver)
+        switch self {
+        case .home:
+            return .init(
+                rootScreen: self,
+                view: tabView,
+                image: Image(systemName: "house"),
+                text: Text("Home")
+            )
+        case .settings:
+            return .init(
+                rootScreen: self,
+                view: tabView,
+                image: Image(systemName: "gear"),
+                text: Text("Settings")
+            )
+        case .onboarding, .login, .requestPermissions, .authorizedRoot:
+            fatalError("Tab for this screen \(self) did not specified")
+        }
+    }
+
+    func modal(resolver: Resolver) -> Main.Modal {
+        .init(screen: self, view: view(resolver: resolver))
+    }
+}

+ 12 - 0
FreeAPS/Sources/Services/Appearance/AppearanceManager.swift

@@ -0,0 +1,12 @@
+import UIKit
+protocol AppearanceManager {
+    func setupGlobalAppearance()
+}
+
+final class BaseAppearanceManager: AppearanceManager {
+    func setupGlobalAppearance() {
+        UITableView.appearance().separatorStyle = .none
+        UITableView.appearance().backgroundColor = .clear
+        UITableViewHeaderFooterView.appearance().tintColor = .clear
+    }
+}

+ 42 - 0
FreeAPS/Sources/Services/AuthorizationManager/AuthorizationManager.swift

@@ -0,0 +1,42 @@
+import AuthenticationServices
+import Combine
+import Swinject
+
+protocol AuthorizationManager {
+    var isAuthorized: Bool { get }
+    var authorizationPublisher: AnyPublisher<Bool, Never> { get }
+    func authorize(credentials: ASAuthorizationAppleIDCredential) -> AnyPublisher<Void, Never>
+    func logout()
+}
+
+final class BaseAuthorizationManager: AuthorizationManager, Injectable {
+    private let isAuthorizedSubject = CurrentValueSubject<Bool, Never>(false)
+
+    var authorizationPublisher: AnyPublisher<Bool, Never> { isAuthorizedSubject.eraseToAnyPublisher() }
+    var isAuthorized: Bool { isAuthorizedSubject.value }
+
+    private var lifetime = Set<AnyCancellable>()
+
+    @Injected() private var keychain: Keychain!
+
+    init(resolver: Resolver) {
+        injectServices(resolver)
+    }
+
+    func authorize(credentials _: ASAuthorizationAppleIDCredential) -> AnyPublisher<Void, Never> {
+        isAuthorizedSubject.send(true)
+        // TODO: send data to server
+        return Just(()).eraseToAnyPublisher()
+    }
+
+    func logout() {
+        keychain.removeObject(forKey: Login.Config.credentialsKey).publisher
+            .sink(
+                receiveCompletion: { _ in
+                    self.isAuthorizedSubject.send(false)
+                },
+                receiveValue: {}
+            )
+            .store(in: &lifetime)
+    }
+}

+ 496 - 0
FreeAPS/Sources/Services/Network/HTTPResponseStatus.swift

@@ -0,0 +1,496 @@
+/// A HTTP response status code.
+public enum HTTPResponseStatus {
+    /* use custom if you want to use a non-standard response code or
+     have it available in a (UInt, String) pair from a higher-level web framework. */
+    case custom(code: UInt, reasonPhrase: String)
+
+    /* all the codes from http://www.iana.org/assignments/http-status-codes */
+
+    // 1xx
+    case `continue`
+    case switchingProtocols
+    case processing
+    // TODO: add '103: Early Hints' (requires bumping SemVer major).
+
+    // 2xx
+    case ok
+    case created
+    case accepted
+    case nonAuthoritativeInformation
+    case noContent
+    case resetContent
+    case partialContent
+    case multiStatus
+    case alreadyReported
+    case imUsed
+
+    // 3xx
+    case multipleChoices
+    case movedPermanently
+    case found
+    case seeOther
+    case notModified
+    case useProxy
+    case temporaryRedirect
+    case permanentRedirect
+
+    // 4xx
+    case badRequest
+    case unauthorized
+    case paymentRequired
+    case forbidden
+    case notFound
+    case methodNotAllowed
+    case notAcceptable
+    case proxyAuthenticationRequired
+    case requestTimeout
+    case conflict
+    case gone
+    case lengthRequired
+    case preconditionFailed
+    case payloadTooLarge
+    case uriTooLong
+    case unsupportedMediaType
+    case rangeNotSatisfiable
+    case expectationFailed
+    case imATeapot
+    case misdirectedRequest
+    case unprocessableEntity
+    case locked
+    case failedDependency
+    case upgradeRequired
+    case preconditionRequired
+    case tooManyRequests
+    case requestHeaderFieldsTooLarge
+    case unavailableForLegalReasons
+
+    // 5xx
+    case internalServerError
+    case notImplemented
+    case badGateway
+    case serviceUnavailable
+    case gatewayTimeout
+    case httpVersionNotSupported
+    case variantAlsoNegotiates
+    case insufficientStorage
+    case loopDetected
+    case notExtended
+    case networkAuthenticationRequired
+
+    /// Whether responses with this status code may have a response body.
+    public var mayHaveResponseBody: Bool {
+        switch self {
+        case .continue,
+             .switchingProtocols,
+             .processing,
+             .noContent,
+             .custom where (code < 200) && (code >= 100):
+            return false
+        default:
+            return true
+        }
+    }
+
+    /// Initialize a `HTTPResponseStatus` from a given status and reason.
+    ///
+    /// - Parameter statusCode: The integer value of the HTTP response status code
+    /// - Parameter reasonPhrase: The textual reason phrase from the response. This will be
+    ///     discarded in favor of the default if the `statusCode` matches one that we know.
+    public init(statusCode: Int, reasonPhrase: String = "") {
+        switch statusCode {
+        case 100:
+            self = .continue
+        case 101:
+            self = .switchingProtocols
+        case 102:
+            self = .processing
+        case 200:
+            self = .ok
+        case 201:
+            self = .created
+        case 202:
+            self = .accepted
+        case 203:
+            self = .nonAuthoritativeInformation
+        case 204:
+            self = .noContent
+        case 205:
+            self = .resetContent
+        case 206:
+            self = .partialContent
+        case 207:
+            self = .multiStatus
+        case 208:
+            self = .alreadyReported
+        case 226:
+            self = .imUsed
+        case 300:
+            self = .multipleChoices
+        case 301:
+            self = .movedPermanently
+        case 302:
+            self = .found
+        case 303:
+            self = .seeOther
+        case 304:
+            self = .notModified
+        case 305:
+            self = .useProxy
+        case 307:
+            self = .temporaryRedirect
+        case 308:
+            self = .permanentRedirect
+        case 400:
+            self = .badRequest
+        case 401:
+            self = .unauthorized
+        case 402:
+            self = .paymentRequired
+        case 403:
+            self = .forbidden
+        case 404:
+            self = .notFound
+        case 405:
+            self = .methodNotAllowed
+        case 406:
+            self = .notAcceptable
+        case 407:
+            self = .proxyAuthenticationRequired
+        case 408:
+            self = .requestTimeout
+        case 409:
+            self = .conflict
+        case 410:
+            self = .gone
+        case 411:
+            self = .lengthRequired
+        case 412:
+            self = .preconditionFailed
+        case 413:
+            self = .payloadTooLarge
+        case 414:
+            self = .uriTooLong
+        case 415:
+            self = .unsupportedMediaType
+        case 416:
+            self = .rangeNotSatisfiable
+        case 417:
+            self = .expectationFailed
+        case 418:
+            self = .imATeapot
+        case 421:
+            self = .misdirectedRequest
+        case 422:
+            self = .unprocessableEntity
+        case 423:
+            self = .locked
+        case 424:
+            self = .failedDependency
+        case 426:
+            self = .upgradeRequired
+        case 428:
+            self = .preconditionRequired
+        case 429:
+            self = .tooManyRequests
+        case 431:
+            self = .requestHeaderFieldsTooLarge
+        case 451:
+            self = .unavailableForLegalReasons
+        case 500:
+            self = .internalServerError
+        case 501:
+            self = .notImplemented
+        case 502:
+            self = .badGateway
+        case 503:
+            self = .serviceUnavailable
+        case 504:
+            self = .gatewayTimeout
+        case 505:
+            self = .httpVersionNotSupported
+        case 506:
+            self = .variantAlsoNegotiates
+        case 507:
+            self = .insufficientStorage
+        case 508:
+            self = .loopDetected
+        case 510:
+            self = .notExtended
+        case 511:
+            self = .networkAuthenticationRequired
+        default:
+            self = .custom(code: UInt(statusCode), reasonPhrase: reasonPhrase)
+        }
+    }
+}
+
+extension HTTPResponseStatus: Equatable {
+    public static func == (lhs: HTTPResponseStatus, rhs: HTTPResponseStatus) -> Bool {
+        switch (lhs, rhs) {
+        case let (.custom(lcode, lreason), .custom(rcode, rreason)):
+            return lcode == rcode && lreason == rreason
+        case (.custom, _), (_, .custom):
+            return false
+        default:
+            return lhs.code == rhs.code
+        }
+    }
+}
+
+extension HTTPResponseStatus {
+    /// The numerical status code for a given HTTP response status.
+    public var code: UInt {
+        switch self {
+        case .continue:
+            return 100
+        case .switchingProtocols:
+            return 101
+        case .processing:
+            return 102
+        case .ok:
+            return 200
+        case .created:
+            return 201
+        case .accepted:
+            return 202
+        case .nonAuthoritativeInformation:
+            return 203
+        case .noContent:
+            return 204
+        case .resetContent:
+            return 205
+        case .partialContent:
+            return 206
+        case .multiStatus:
+            return 207
+        case .alreadyReported:
+            return 208
+        case .imUsed:
+            return 226
+        case .multipleChoices:
+            return 300
+        case .movedPermanently:
+            return 301
+        case .found:
+            return 302
+        case .seeOther:
+            return 303
+        case .notModified:
+            return 304
+        case .useProxy:
+            return 305
+        case .temporaryRedirect:
+            return 307
+        case .permanentRedirect:
+            return 308
+        case .badRequest:
+            return 400
+        case .unauthorized:
+            return 401
+        case .paymentRequired:
+            return 402
+        case .forbidden:
+            return 403
+        case .notFound:
+            return 404
+        case .methodNotAllowed:
+            return 405
+        case .notAcceptable:
+            return 406
+        case .proxyAuthenticationRequired:
+            return 407
+        case .requestTimeout:
+            return 408
+        case .conflict:
+            return 409
+        case .gone:
+            return 410
+        case .lengthRequired:
+            return 411
+        case .preconditionFailed:
+            return 412
+        case .payloadTooLarge:
+            return 413
+        case .uriTooLong:
+            return 414
+        case .unsupportedMediaType:
+            return 415
+        case .rangeNotSatisfiable:
+            return 416
+        case .expectationFailed:
+            return 417
+        case .imATeapot:
+            return 418
+        case .misdirectedRequest:
+            return 421
+        case .unprocessableEntity:
+            return 422
+        case .locked:
+            return 423
+        case .failedDependency:
+            return 424
+        case .upgradeRequired:
+            return 426
+        case .preconditionRequired:
+            return 428
+        case .tooManyRequests:
+            return 429
+        case .requestHeaderFieldsTooLarge:
+            return 431
+        case .unavailableForLegalReasons:
+            return 451
+        case .internalServerError:
+            return 500
+        case .notImplemented:
+            return 501
+        case .badGateway:
+            return 502
+        case .serviceUnavailable:
+            return 503
+        case .gatewayTimeout:
+            return 504
+        case .httpVersionNotSupported:
+            return 505
+        case .variantAlsoNegotiates:
+            return 506
+        case .insufficientStorage:
+            return 507
+        case .loopDetected:
+            return 508
+        case .notExtended:
+            return 510
+        case .networkAuthenticationRequired:
+            return 511
+        case .custom(code: let code, reasonPhrase: _):
+            return code
+        }
+    }
+
+    /// The string reason phrase for a given HTTP response status.
+    public var reasonPhrase: String {
+        switch self {
+        case .continue:
+            return "Continue"
+        case .switchingProtocols:
+            return "Switching Protocols"
+        case .processing:
+            return "Processing"
+        case .ok:
+            return "OK"
+        case .created:
+            return "Created"
+        case .accepted:
+            return "Accepted"
+        case .nonAuthoritativeInformation:
+            return "Non-Authoritative Information"
+        case .noContent:
+            return "No Content"
+        case .resetContent:
+            return "Reset Content"
+        case .partialContent:
+            return "Partial Content"
+        case .multiStatus:
+            return "Multi-Status"
+        case .alreadyReported:
+            return "Already Reported"
+        case .imUsed:
+            return "IM Used"
+        case .multipleChoices:
+            return "Multiple Choices"
+        case .movedPermanently:
+            return "Moved Permanently"
+        case .found:
+            return "Found"
+        case .seeOther:
+            return "See Other"
+        case .notModified:
+            return "Not Modified"
+        case .useProxy:
+            return "Use Proxy"
+        case .temporaryRedirect:
+            return "Temporary Redirect"
+        case .permanentRedirect:
+            return "Permanent Redirect"
+        case .badRequest:
+            return "Bad Request"
+        case .unauthorized:
+            return "Unauthorized"
+        case .paymentRequired:
+            return "Payment Required"
+        case .forbidden:
+            return "Forbidden"
+        case .notFound:
+            return "Not Found"
+        case .methodNotAllowed:
+            return "Method Not Allowed"
+        case .notAcceptable:
+            return "Not Acceptable"
+        case .proxyAuthenticationRequired:
+            return "Proxy Authentication Required"
+        case .requestTimeout:
+            return "Request Timeout"
+        case .conflict:
+            return "Conflict"
+        case .gone:
+            return "Gone"
+        case .lengthRequired:
+            return "Length Required"
+        case .preconditionFailed:
+            return "Precondition Failed"
+        case .payloadTooLarge:
+            return "Payload Too Large"
+        case .uriTooLong:
+            return "URI Too Long"
+        case .unsupportedMediaType:
+            return "Unsupported Media Type"
+        case .rangeNotSatisfiable:
+            return "Range Not Satisfiable"
+        case .expectationFailed:
+            return "Expectation Failed"
+        case .imATeapot:
+            return "I'm a teapot"
+        case .misdirectedRequest:
+            return "Misdirected Request"
+        case .unprocessableEntity:
+            return "Unprocessable Entity"
+        case .locked:
+            return "Locked"
+        case .failedDependency:
+            return "Failed Dependency"
+        case .upgradeRequired:
+            return "Upgrade Required"
+        case .preconditionRequired:
+            return "Precondition Required"
+        case .tooManyRequests:
+            return "Too Many Requests"
+        case .requestHeaderFieldsTooLarge:
+            return "Request Header Fields Too Large"
+        case .unavailableForLegalReasons:
+            return "Unavailable For Legal Reasons"
+        case .internalServerError:
+            return "Internal Server Error"
+        case .notImplemented:
+            return "Not Implemented"
+        case .badGateway:
+            return "Bad Gateway"
+        case .serviceUnavailable:
+            return "Service Unavailable"
+        case .gatewayTimeout:
+            return "Gateway Timeout"
+        case .httpVersionNotSupported:
+            return "HTTP Version Not Supported"
+        case .variantAlsoNegotiates:
+            return "Variant Also Negotiates"
+        case .insufficientStorage:
+            return "Insufficient Storage"
+        case .loopDetected:
+            return "Loop Detected"
+        case .notExtended:
+            return "Not Extended"
+        case .networkAuthenticationRequired:
+            return "Network Authentication Required"
+        case .custom(code: _, reasonPhrase: let phrase):
+            return phrase
+        }
+    }
+}

+ 27 - 0
FreeAPS/Sources/Services/Network/NetworkManager.swift

@@ -0,0 +1,27 @@
+import Combine
+import Foundation
+import Moya
+
+protocol NetworkManager {
+    func upload(classifier: String, id: String, image: Data) -> AnyPublisher<HTTPResponseStatus?, MoyaError>
+}
+
+final class BaseNetworkManager: NetworkManager {
+    private let remote = MoyaProvider<RemoteService>()
+
+    func upload(classifier: String, id: String, image: Data) -> AnyPublisher<HTTPResponseStatus?, MoyaError> {
+        Deferred {
+            Future<Response, MoyaError> { promise in
+                self.remote.request(
+                    .upload(
+                        classifier: classifier,
+                        id: id,
+                        image: image
+                    ),
+                    completion: promise
+                )
+            }
+            .map { $0.response.flatMap { HTTPResponseStatus(statusCode: $0.statusCode) } }
+        }.eraseToAnyPublisher()
+    }
+}

+ 62 - 0
FreeAPS/Sources/Services/Network/RemoteService.swift

@@ -0,0 +1,62 @@
+import Foundation
+import Moya
+
+enum RemoteService {
+    case login
+    case upload(classifier: String, id: String, image: Data)
+}
+
+extension RemoteService: TargetType {
+    var baseURL: URL { URL(string: "http://94.141.168.254:8080")! }
+
+    var path: String {
+        switch self {
+        case .login:
+            return "login"
+        case .upload:
+            return "upload"
+        }
+    }
+
+    var method: Moya.Method {
+        switch self {
+        case .login:
+            return .post
+        case .upload:
+            return .post
+        }
+    }
+
+    var sampleData: Data {
+        Data()
+    }
+
+    var task: Task {
+        switch self {
+        case .login:
+            return .requestPlain
+        case let .upload(classifier, id, image):
+            return .uploadMultipart(
+                [
+                    .init(provider: .data(classifier.data(using: .utf8)!), name: "classifier"),
+                    .init(provider: .data(id.data(using: .utf8)!), name: "id"),
+                    .init(
+                        provider: .data(image),
+                        name: "image",
+                        fileName: "image.jpeg",
+                        mimeType: "image/jpeg"
+                    )
+                ]
+            )
+        }
+    }
+
+    var headers: [String: String]? {
+        switch self {
+        case .login:
+            return ["Content-type": "application/json"]
+        case .upload:
+            return ["Content-type": "multipart/form-data"]
+        }
+    }
+}

+ 0 - 0
FreeAPS/Sources/Services/Storage/Cache/Cache.swift


Некоторые файлы не были показаны из-за большого количества измененных файлов