Jelajahi Sumber

NightscoutConfig

Ivan Valkou 5 tahun lalu
induk
melakukan
4a57ab86b3

+ 44 - 21
FreeAPS.xcodeproj/project.pbxproj

@@ -55,7 +55,6 @@
 		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 */; };
@@ -66,7 +65,6 @@
 		3811DEB225C9D88300A708ED /* KeychainItemAccessibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DEA025C9D88300A708ED /* KeychainItemAccessibility.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 */; };
@@ -90,14 +88,23 @@
 		388E5A5C25B6F0770019842D /* JSON.swift in Sources */ = {isa = PBXBuildFile; fileRef = 388E5A5B25B6F0770019842D /* JSON.swift */; };
 		388E5A6025B6F2310019842D /* Autosens.swift in Sources */ = {isa = PBXBuildFile; fileRef = 388E5A5F25B6F2310019842D /* Autosens.swift */; };
 		3895E4C625B9E00D00214B37 /* Preferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3895E4C525B9E00D00214B37 /* Preferences.swift */; };
+		38FE826A25CC82DB001FF17A /* NetworkService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38FE826925CC82DB001FF17A /* NetworkService.swift */; };
+		38FE826D25CC8461001FF17A /* NightscoutAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38FE826C25CC8461001FF17A /* NightscoutAPI.swift */; };
 		45252C95D220E796FDB3B022 /* ConfigEditorDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F8A87AA037BD079BA3528BA /* ConfigEditorDataFlow.swift */; };
 		45717281F743594AA9D87191 /* ConfigEditorRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 920DDB21E5D0EB813197500D /* ConfigEditorRootView.swift */; };
+		642F76A05A4FF530463A9FD0 /* NightscoutConfigRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8782B44544F38F2B2D82C38E /* NightscoutConfigRootView.swift */; };
 		72F1BD388F42FCA6C52E4500 /* ConfigEditorProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44080E4709E3AE4B73054563 /* ConfigEditorProvider.swift */; };
+		9825E5E923F0B8FA80C8C7C7 /* NightscoutConfigViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = A0A48AE3AC813A49A517846A /* NightscoutConfigViewModel.swift */; };
+		BD2B464E0745FBE7B79913F4 /* NightscoutConfigProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BF768BD6264FF7D71D66767 /* NightscoutConfigProvider.swift */; };
+		CDB87FA71A93F3739D3D338E /* NightscoutConfigBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 111579A6E3AC6BFA79C4DD43 /* NightscoutConfigBuilder.swift */; };
+		D6DEC113821A7F1056C4AA1E /* NightscoutConfigDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F2A13DF0EDEEEDC4106AA2A /* NightscoutConfigDataFlow.swift */; };
 		E102DE9C3E9C8AEDCB3C61BB /* ConfigEditorBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = E492D5B2EEF2119977EA2CE4 /* ConfigEditorBuilder.swift */; };
 		E39E418C56A5A46B61D960EE /* ConfigEditorViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D5B4F8B4194BB7E260EF251 /* ConfigEditorViewModel.swift */; };
 /* End PBXBuildFile section */
 
 /* Begin PBXFileReference section */
+		111579A6E3AC6BFA79C4DD43 /* NightscoutConfigBuilder.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = NightscoutConfigBuilder.swift; sourceTree = "<group>"; };
+		2F2A13DF0EDEEEDC4106AA2A /* NightscoutConfigDataFlow.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = NightscoutConfigDataFlow.swift; sourceTree = "<group>"; };
 		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>"; };
@@ -145,7 +152,6 @@
 		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>"; };
@@ -182,10 +188,15 @@
 		388E5A5B25B6F0770019842D /* JSON.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSON.swift; sourceTree = "<group>"; };
 		388E5A5F25B6F2310019842D /* Autosens.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Autosens.swift; sourceTree = "<group>"; };
 		3895E4C525B9E00D00214B37 /* Preferences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Preferences.swift; sourceTree = "<group>"; };
+		38FE826925CC82DB001FF17A /* NetworkService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkService.swift; sourceTree = "<group>"; };
+		38FE826C25CC8461001FF17A /* NightscoutAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NightscoutAPI.swift; sourceTree = "<group>"; };
+		3BF768BD6264FF7D71D66767 /* NightscoutConfigProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = NightscoutConfigProvider.swift; sourceTree = "<group>"; };
 		3F8A87AA037BD079BA3528BA /* ConfigEditorDataFlow.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ConfigEditorDataFlow.swift; sourceTree = "<group>"; };
 		44080E4709E3AE4B73054563 /* ConfigEditorProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ConfigEditorProvider.swift; sourceTree = "<group>"; };
 		5D5B4F8B4194BB7E260EF251 /* ConfigEditorViewModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ConfigEditorViewModel.swift; sourceTree = "<group>"; };
+		8782B44544F38F2B2D82C38E /* NightscoutConfigRootView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = NightscoutConfigRootView.swift; sourceTree = "<group>"; };
 		920DDB21E5D0EB813197500D /* ConfigEditorRootView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ConfigEditorRootView.swift; sourceTree = "<group>"; };
+		A0A48AE3AC813A49A517846A /* NightscoutConfigViewModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = NightscoutConfigViewModel.swift; sourceTree = "<group>"; };
 		E492D5B2EEF2119977EA2CE4 /* ConfigEditorBuilder.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ConfigEditorBuilder.swift; sourceTree = "<group>"; };
 /* End PBXFileReference section */
 
@@ -194,7 +205,6 @@
 			isa = PBXFrameworksBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
-				3811DEBB25C9D8DD00A708ED /* Moya in Frameworks */,
 				3811DE1025C9D37700A708ED /* Swinject in Frameworks */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
@@ -226,6 +236,7 @@
 				3811DE8125C9D6DD00A708ED /* RequestPermissions */,
 				3811DE3825C9D4A100A708ED /* Settings */,
 				0610F7D6D2EC00E3BA1569F0 /* ConfigEditor */,
+				D533BF261CDC1C3F871E7BFD /* NightscoutConfig */,
 			);
 			path = Modules;
 			sourceTree = "<group>";
@@ -428,9 +439,10 @@
 		3811DE9425C9D88200A708ED /* Network */ = {
 			isa = PBXGroup;
 			children = (
-				3811DE9525C9D88200A708ED /* RemoteService.swift */,
 				3811DE9625C9D88300A708ED /* HTTPResponseStatus.swift */,
 				3811DE9725C9D88300A708ED /* NetworkManager.swift */,
+				38FE826925CC82DB001FF17A /* NetworkService.swift */,
+				38FE826C25CC8461001FF17A /* NightscoutAPI.swift */,
 			);
 			path = Network;
 			sourceTree = "<group>";
@@ -609,6 +621,26 @@
 			path = View;
 			sourceTree = "<group>";
 		};
+		4F4AE4D901E8BA872B207D7F /* View */ = {
+			isa = PBXGroup;
+			children = (
+				8782B44544F38F2B2D82C38E /* NightscoutConfigRootView.swift */,
+			);
+			path = View;
+			sourceTree = "<group>";
+		};
+		D533BF261CDC1C3F871E7BFD /* NightscoutConfig */ = {
+			isa = PBXGroup;
+			children = (
+				111579A6E3AC6BFA79C4DD43 /* NightscoutConfigBuilder.swift */,
+				2F2A13DF0EDEEEDC4106AA2A /* NightscoutConfigDataFlow.swift */,
+				3BF768BD6264FF7D71D66767 /* NightscoutConfigProvider.swift */,
+				A0A48AE3AC813A49A517846A /* NightscoutConfigViewModel.swift */,
+				4F4AE4D901E8BA872B207D7F /* View */,
+			);
+			path = NightscoutConfig;
+			sourceTree = "<group>";
+		};
 /* End PBXGroup section */
 
 /* Begin PBXNativeTarget section */
@@ -628,7 +660,6 @@
 			name = FreeAPS;
 			packageProductDependencies = (
 				3811DE0F25C9D37700A708ED /* Swinject */,
-				3811DEBA25C9D8DD00A708ED /* Moya */,
 			);
 			productName = FreeAPS;
 			productReference = 388E595825AD948C0019842D /* FreeAPS.app */;
@@ -659,7 +690,6 @@
 			mainGroup = 388E594F25AD948C0019842D;
 			packageReferences = (
 				3811DE0E25C9D37700A708ED /* XCRemoteSwiftPackageReference "Swinject" */,
-				3811DEB925C9D8DD00A708ED /* XCRemoteSwiftPackageReference "Moya" */,
 			);
 			productRefGroup = 388E595925AD948C0019842D /* Products */;
 			projectDirPath = "";
@@ -748,6 +778,7 @@
 				3811DE1825C9D40400A708ED /* Router.swift in Sources */,
 				3811DEE825CA063400A708ED /* Injected.swift in Sources */,
 				3811DEAF25C9D88300A708ED /* KeyValueStorage.swift in Sources */,
+				38FE826D25CC8461001FF17A /* NightscoutAPI.swift in Sources */,
 				3811DE4E25C9D4B800A708ED /* AuthotizedRootRootView.swift in Sources */,
 				3811DE7D25C9D6D300A708ED /* LoginRootView.swift in Sources */,
 				3811DE4025C9D4A100A708ED /* SettingsBuilder.swift in Sources */,
@@ -761,7 +792,6 @@
 				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 */,
@@ -777,6 +807,7 @@
 				3811DEEA25CA063400A708ED /* SyncAccess.swift in Sources */,
 				3811DE4F25C9D4B800A708ED /* AuthotizedRootDataFlow.swift in Sources */,
 				3811DE5025C9D4B800A708ED /* AuthotizedRootProvider.swift in Sources */,
+				38FE826A25CC82DB001FF17A /* NetworkService.swift in Sources */,
 				3811DE6C25C9D62600A708ED /* OnboardingDataFlow.swift in Sources */,
 				3811DE2425C9D48300A708ED /* MainViewModel.swift in Sources */,
 				3811DE3F25C9D4A100A708ED /* SettingsViewModel.swift in Sources */,
@@ -792,6 +823,11 @@
 				72F1BD388F42FCA6C52E4500 /* ConfigEditorProvider.swift in Sources */,
 				E39E418C56A5A46B61D960EE /* ConfigEditorViewModel.swift in Sources */,
 				45717281F743594AA9D87191 /* ConfigEditorRootView.swift in Sources */,
+				CDB87FA71A93F3739D3D338E /* NightscoutConfigBuilder.swift in Sources */,
+				D6DEC113821A7F1056C4AA1E /* NightscoutConfigDataFlow.swift in Sources */,
+				BD2B464E0745FBE7B79913F4 /* NightscoutConfigProvider.swift in Sources */,
+				9825E5E923F0B8FA80C8C7C7 /* NightscoutConfigViewModel.swift in Sources */,
+				642F76A05A4FF530463A9FD0 /* NightscoutConfigRootView.swift in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -992,14 +1028,6 @@
 				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 */
@@ -1008,11 +1036,6 @@
 			package = 3811DE0E25C9D37700A708ED /* XCRemoteSwiftPackageReference "Swinject" */;
 			productName = Swinject;
 		};
-		3811DEBA25C9D8DD00A708ED /* Moya */ = {
-			isa = XCSwiftPackageProductDependency;
-			package = 3811DEB925C9D8DD00A708ED /* XCRemoteSwiftPackageReference "Moya" */;
-			productName = Moya;
-		};
 /* End XCSwiftPackageProductDependency section */
 	};
 	rootObject = 388E595025AD948C0019842D /* Project object */;

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

@@ -2,42 +2,6 @@
   "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": {

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

@@ -83,6 +83,23 @@ struct AdaptsToSoftwareKeyboard: ViewModifier {
     }
 }
 
+struct ClearButton: ViewModifier
+{
+    @Binding var text: String
+    func body(content: Content) -> some View {
+        HStack {
+            content
+            if !text.isEmpty {
+                Button { self.text = "" }
+                label: {
+                    Image(systemName: "delete.left")
+                        .foregroundColor(.gray)
+                }
+            }
+        }
+    }
+}
+
 extension View {
     func roundedBackground() -> some View {
         modifier(RoundedBackground())

+ 3 - 3
FreeAPS/Sources/Modules/Base/BaseModuleBuilder.swift

@@ -3,7 +3,7 @@ import Swinject
 
 protocol ModuleBuilder {
     associatedtype View: SwiftUI.View
-    func buildView() -> View
+    func buildView() -> AnyView
 }
 
 class BaseModuleBuilder<View: BaseView, ViewModel: ObservableObject, Provider: FreeAPS.Provider>: ModuleBuilder
@@ -20,7 +20,7 @@ class BaseModuleBuilder<View: BaseView, ViewModel: ObservableObject, Provider: F
         ViewModel(provider: Provider(resolver: resolver), resolver: resolver)
     }
 
-    func buildView() -> some SwiftUI.View {
-        View().environmentObject(viewModel)
+    func buildView() -> AnyView {
+        View().environmentObject(viewModel).asAny()
     }
 }

+ 3 - 0
FreeAPS/Sources/Modules/NightscoutConfig/NightscoutConfigBuilder.swift

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

+ 8 - 0
FreeAPS/Sources/Modules/NightscoutConfig/NightscoutConfigDataFlow.swift

@@ -0,0 +1,8 @@
+enum NightscoutConfig {
+    enum Config {
+        static let urlKey = "NightscoutConfig.url"
+        static let secretKey = "NightscoutConfig.secret"
+    }
+}
+
+protocol NightscoutConfigProvider: Provider {}

+ 3 - 0
FreeAPS/Sources/Modules/NightscoutConfig/NightscoutConfigProvider.swift

@@ -0,0 +1,3 @@
+extension NightscoutConfig {
+    final class Provider: BaseProvider, NightscoutConfigProvider {}
+}

+ 28 - 0
FreeAPS/Sources/Modules/NightscoutConfig/NightscoutConfigViewModel.swift

@@ -0,0 +1,28 @@
+import SwiftUI
+
+extension NightscoutConfig {
+    class ViewModel<Provider>: BaseViewModel<Provider>, ObservableObject where Provider: NightscoutConfigProvider {
+        @Injected() var keychain: Keychain!
+
+        @Published var url = ""
+        @Published var secret = ""
+
+        override func subscribe() {
+            url = keychain.getValue(String.self, forKey: Config.urlKey) ?? ""
+            secret = keychain.getValue(String.self, forKey: Config.secretKey) ?? ""
+        }
+
+        func connect() {
+            // TODO: check connection
+            keychain.setValue(url, forKey: Config.urlKey)
+            keychain.setValue(url, forKey: Config.secretKey)
+        }
+
+        func delete() {
+            keychain.removeObject(forKey: Config.urlKey)
+            keychain.removeObject(forKey: Config.secretKey)
+            url = ""
+            secret = ""
+        }
+    }
+}

+ 27 - 0
FreeAPS/Sources/Modules/NightscoutConfig/View/NightscoutConfigRootView.swift

@@ -0,0 +1,27 @@
+import SwiftUI
+
+extension NightscoutConfig {
+    struct RootView: BaseView {
+        @EnvironmentObject var viewModel: ViewModel<Provider>
+
+        var body: some View {
+            Form {
+                TextField("URL", text: $viewModel.url)
+                    .disableAutocorrection(true)
+                    .textContentType(.URL)
+                    .autocapitalization(.none)
+                    .keyboardType(.URL)
+                SecureField("API secret", text: $viewModel.secret)
+                    .disableAutocorrection(true)
+                    .autocapitalization(.none)
+                    .textContentType(.password)
+                    .keyboardType(.asciiCapable)
+                Button("Connect") { viewModel.connect() }.disabled(viewModel.url.isEmpty || viewModel.secret.isEmpty)
+                Button("Delete") { viewModel.delete() }.foregroundColor(.red)
+            }
+            .toolbar { ToolbarItem(placement: .principal) { Text("Nightscout Config") } }
+            .navigationBarItems(leading: Button("Close", action: viewModel.hideModal))
+            .navigationBarTitleDisplayMode(.inline)
+        }
+    }
+}

+ 3 - 10
FreeAPS/Sources/Modules/Settings/View/SettingsRootView.swift

@@ -5,17 +5,10 @@ extension Settings {
         @EnvironmentObject var viewModel: ViewModel<Provider>
 
         var body: some View {
-            VStack {
-                Text("Settings screen")
-                Button(action: viewModel.openProfileEditor) {
-                    Text("Open Editor")
-                        .frame(maxWidth: .infinity)
-                        .foregroundColor(.white)
-                        .buttonBackground()
-                }
-                Spacer()
+            Form {
+                Text("Open Editor").modal(for: .configEditor, from: self)
+                Text("Nightscout").modal(for: .nighscoutConfig, from: self)
             }
-            .padding()
             .toolbar { ToolbarItem(placement: .principal) { Text("Settings") } }
         }
     }

+ 11 - 7
FreeAPS/Sources/Router/Screen.swift

@@ -9,6 +9,7 @@ enum Screen: Identifiable {
     case login
     case requestPermissions
     case configEditor
+    case nighscoutConfig
 
     var id: Int { String(reflecting: self).hashValue }
 }
@@ -17,19 +18,21 @@ extension Screen {
     func view(resolver: Resolver) -> AnyView {
         switch self {
         case .home:
-            return Home.Builder(resolver: resolver).buildView().asAny()
+            return Home.Builder(resolver: resolver).buildView()
         case .settings:
-            return Settings.Builder(resolver: resolver).buildView().asAny()
+            return Settings.Builder(resolver: resolver).buildView()
         case .onboarding:
-            return Onboarding.Builder(resolver: resolver).buildView().asAny()
+            return Onboarding.Builder(resolver: resolver).buildView()
         case .authorizedRoot:
-            return AuthotizedRoot.Builder(resolver: resolver).buildView().asAny()
+            return AuthotizedRoot.Builder(resolver: resolver).buildView()
         case .login:
-            return Login.Builder(resolver: resolver).buildView().asAny()
+            return Login.Builder(resolver: resolver).buildView()
         case .requestPermissions:
-            return RequestPermissions.Builder(resolver: resolver).buildView().asAny()
+            return RequestPermissions.Builder(resolver: resolver).buildView()
         case .configEditor:
-            return ConfigEditor.Builder(resolver: resolver).buildView().asAny()
+            return ConfigEditor.Builder(resolver: resolver).buildView()
+        case .nighscoutConfig:
+            return NightscoutConfig.Builder(resolver: resolver).buildView()
         }
     }
 
@@ -53,6 +56,7 @@ extension Screen {
         case .authorizedRoot,
              .configEditor,
              .login,
+             .nighscoutConfig,
              .onboarding,
              .requestPermissions:
             fatalError("Tab for this screen \(self) did not specified")

+ 2 - 23
FreeAPS/Sources/Services/Network/NetworkManager.swift

@@ -1,27 +1,6 @@
 import Combine
 import Foundation
-import Moya
 
-protocol NetworkManager {
-    func upload(classifier: String, id: String, image: Data) -> AnyPublisher<HTTPResponseStatus?, MoyaError>
-}
+protocol NetworkManager {}
 
-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()
-    }
-}
+final class BaseNetworkManager: NetworkManager {}

+ 19 - 0
FreeAPS/Sources/Services/Network/NetworkService.swift

@@ -0,0 +1,19 @@
+import Combine
+import Foundation
+
+struct NetworkService {
+    struct Response<T> {
+        let value: T
+        let response: URLResponse
+    }
+
+    func run<T: Decodable>(_ request: URLRequest, decoder: JSONDecoder = JSONDecoder()) -> AnyPublisher<Response<T>, Error> {
+        URLSession.shared
+            .dataTaskPublisher(for: request)
+            .tryMap { result -> Response<T> in
+                let value = try decoder.decode(T.self, from: result.data)
+                return Response(value: value, response: result.response)
+            }
+            .eraseToAnyPublisher()
+    }
+}

+ 7 - 0
FreeAPS/Sources/Services/Network/NightscoutAPI.swift

@@ -0,0 +1,7 @@
+import Foundation
+
+struct NightscoutAPI {
+    let base: URL
+}
+
+extension NightscoutAPI {}

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

@@ -1,62 +0,0 @@
-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"]
-        }
-    }
-}

+ 2 - 2
FreeAPS/Sources/Services/Storage/Keychain/Keychain.swift

@@ -16,8 +16,8 @@ protocol Keychain: KeyValueStorage {
     @discardableResult func setData(_ value: Data, forKey key: String) -> Result<Void, KeychainError>
     @discardableResult func setValue<T: Encodable>(_ maybeValue: T?, forKey key: String) -> Result<Void, KeychainError>
 
-    func removeObject(forKey key: String) -> Result<Void, KeychainError>
-    func removeAllKeys() -> Result<Void, KeychainError>
+    @discardableResult func removeObject(forKey key: String) -> Result<Void, KeychainError>
+    @discardableResult func removeAllKeys() -> Result<Void, KeychainError>
 
     static func wipeKeychain()
 }

+ 3 - 1
Templates/swift_pvvm/Code/ViewModel.swift.liquid

@@ -1,5 +1,7 @@
 import SwiftUI
 
 extension {{ module_info.name }} {
-    class ViewModel<Provider>: BaseViewModel<Provider>, ObservableObject where Provider: {{ module_info.name }}Provider {}
+    class ViewModel<Provider>: BaseViewModel<Provider>, ObservableObject where Provider: {{ module_info.name }}Provider {
+    	override func subscribe() {}
+    }
 }