فهرست منبع

Animated background to improve your mood

Ivan Valkou 4 سال پیش
والد
کامیت
1831a3f3e0

+ 22 - 2
FreeAPS.xcodeproj/project.pbxproj

@@ -167,6 +167,9 @@
 		38DAB28A260D349500F74C1A /* FetchGlucoseManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38DAB289260D349500F74C1A /* FetchGlucoseManager.swift */; };
 		38DF1786276A73D400B3528F /* TagCloudView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38DF1785276A73D400B3528F /* TagCloudView.swift */; };
 		38DF1789276FC8C400B3528F /* SwiftMessages in Frameworks */ = {isa = PBXBuildFile; productRef = 38DF1788276FC8C400B3528F /* SwiftMessages */; };
+		38DF178D27733E6800B3528F /* snow.sks in Resources */ = {isa = PBXBuildFile; fileRef = 38DF178B27733E6800B3528F /* snow.sks */; };
+		38DF178E27733E6800B3528F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 38DF178C27733E6800B3528F /* Assets.xcassets */; };
+		38DF179027733EAD00B3528F /* SnowScene.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38DF178F27733EAD00B3528F /* SnowScene.swift */; };
 		38E4451E274DB04600EC9A94 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38E4451D274DB04600EC9A94 /* AppDelegate.swift */; };
 		38E44522274E3DDC00EC9A94 /* NetworkReachabilityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38E44521274E3DDC00EC9A94 /* NetworkReachabilityManager.swift */; };
 		38E44528274E401C00EC9A94 /* Protected.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38E44527274E401C00EC9A94 /* Protected.swift */; };
@@ -569,6 +572,9 @@
 		38DAB27F260CBB7F00F74C1A /* PumpView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PumpView.swift; sourceTree = "<group>"; };
 		38DAB289260D349500F74C1A /* FetchGlucoseManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FetchGlucoseManager.swift; sourceTree = "<group>"; };
 		38DF1785276A73D400B3528F /* TagCloudView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagCloudView.swift; sourceTree = "<group>"; };
+		38DF178B27733E6800B3528F /* snow.sks */ = {isa = PBXFileReference; lastKnownFileType = file.sks; path = snow.sks; sourceTree = "<group>"; };
+		38DF178C27733E6800B3528F /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
+		38DF178F27733EAD00B3528F /* SnowScene.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SnowScene.swift; sourceTree = "<group>"; };
 		38E4451D274DB04600EC9A94 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
 		38E44521274E3DDC00EC9A94 /* NetworkReachabilityManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkReachabilityManager.swift; sourceTree = "<group>"; };
 		38E44527274E401C00EC9A94 /* Protected.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Protected.swift; sourceTree = "<group>"; };
@@ -886,12 +892,13 @@
 		3811DE1325C9D39E00A708ED /* Sources */ = {
 			isa = PBXGroup;
 			children = (
-				E00EEBFC27368630002FF094 /* Assemblies */,
-				198377CF266BFEDE004DE65E /* Localizations */,
 				3811DEDE25C9E2DD00A708ED /* Application */,
 				3811DF0A25CAAAA500A708ED /* APS */,
+				E00EEBFC27368630002FF094 /* Assemblies */,
 				38E98A3225F5300800C0CED0 /* Config */,
 				388E5A5A25B6F05F0019842D /* Helpers */,
+				38DF178A27733E0F00B3528F /* AnimatedBackground */,
+				198377CF266BFEDE004DE65E /* Localizations */,
 				38E98A1A25F52C9300C0CED0 /* Logger */,
 				388E5A5925B6F0250019842D /* Models */,
 				3811DE0325C9D31700A708ED /* Modules */,
@@ -1343,6 +1350,16 @@
 			path = SwiftNotificationCenter;
 			sourceTree = "<group>";
 		};
+		38DF178A27733E0F00B3528F /* AnimatedBackground */ = {
+			isa = PBXGroup;
+			children = (
+				38DF178B27733E6800B3528F /* snow.sks */,
+				38DF178C27733E6800B3528F /* Assets.xcassets */,
+				38DF178F27733EAD00B3528F /* SnowScene.swift */,
+			);
+			path = AnimatedBackground;
+			sourceTree = "<group>";
+		};
 		38E44529274E40F100EC9A94 /* Disk */ = {
 			isa = PBXGroup;
 			children = (
@@ -1988,7 +2005,9 @@
 			files = (
 				388E596025AD948E0019842D /* Assets.xcassets in Resources */,
 				198377D2266BFFF6004DE65E /* Localizable.strings in Resources */,
+				38DF178D27733E6800B3528F /* snow.sks in Resources */,
 				388E597225AD9CF10019842D /* json in Resources */,
+				38DF178E27733E6800B3528F /* Assets.xcassets in Resources */,
 				388E596F25AD96040019842D /* javascript in Resources */,
 				1927C8E62744606D00347C69 /* InfoPlist.strings in Resources */,
 			);
@@ -2166,6 +2185,7 @@
 				45252C95D220E796FDB3B022 /* ConfigEditorDataFlow.swift in Sources */,
 				3871F38725ED661C0013ECB5 /* Suggestion.swift in Sources */,
 				38C4D33A25E9A1ED00D30B77 /* NSObject+AssociatedValues.swift in Sources */,
+				38DF179027733EAD00B3528F /* SnowScene.swift in Sources */,
 				38AAF8712600C1B0004AF583 /* MainChartView.swift in Sources */,
 				38E4453A274E411700EC9A94 /* Disk+[UIImage].swift in Sources */,
 				72F1BD388F42FCA6C52E4500 /* ConfigEditorProvider.swift in Sources */,

+ 2 - 1
FreeAPS/Resources/json/defaults/freeaps/freeaps_settings.json

@@ -18,5 +18,6 @@
     "lowGlucose": 72,
     "highGlucose": 270,
     "carbsRequiredThreshold": 10,
-    "useAppleHealth": false
+    "useAppleHealth": false,
+    "animatedBackground": false
 }

+ 6 - 0
FreeAPS/Sources/AnimatedBackground/Assets.xcassets/Contents.json

@@ -0,0 +1,6 @@
+{
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}

+ 6 - 0
FreeAPS/Sources/AnimatedBackground/Assets.xcassets/Particle Sprite Atlas.spriteatlas/Contents.json

@@ -0,0 +1,6 @@
+{
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}

+ 21 - 0
FreeAPS/Sources/AnimatedBackground/Assets.xcassets/Particle Sprite Atlas.spriteatlas/bokeh.imageset/Contents.json

@@ -0,0 +1,21 @@
+{
+  "images" : [
+    {
+      "filename" : "bokeh.png",
+      "idiom" : "universal",
+      "scale" : "1x"
+    },
+    {
+      "idiom" : "universal",
+      "scale" : "2x"
+    },
+    {
+      "idiom" : "universal",
+      "scale" : "3x"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}

BIN
FreeAPS/Sources/AnimatedBackground/Assets.xcassets/Particle Sprite Atlas.spriteatlas/bokeh.imageset/bokeh.png


+ 21 - 0
FreeAPS/Sources/AnimatedBackground/Assets.xcassets/Particle Sprite Atlas.spriteatlas/spark.imageset/Contents.json

@@ -0,0 +1,21 @@
+{
+  "images" : [
+    {
+      "filename" : "spark.png",
+      "idiom" : "universal",
+      "scale" : "1x"
+    },
+    {
+      "idiom" : "universal",
+      "scale" : "2x"
+    },
+    {
+      "idiom" : "universal",
+      "scale" : "3x"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}

BIN
FreeAPS/Sources/AnimatedBackground/Assets.xcassets/Particle Sprite Atlas.spriteatlas/spark.imageset/spark.png


+ 19 - 0
FreeAPS/Sources/AnimatedBackground/SnowScene.swift

@@ -0,0 +1,19 @@
+import SpriteKit
+
+class SnowScene: SKScene {
+    let snowEmitterNode = SKEmitterNode(fileNamed: "snow.sks")
+
+    override func didMove(to _: SKView) {
+        guard let snowEmitterNode = snowEmitterNode else { return }
+        snowEmitterNode.particleSize = CGSize(width: 50, height: 50)
+        snowEmitterNode.particleLifetime = 2
+        snowEmitterNode.particleLifetimeRange = 6
+        addChild(snowEmitterNode)
+    }
+
+    override func didChangeSize(_: CGSize) {
+        guard let snowEmitterNode = snowEmitterNode else { return }
+        snowEmitterNode.particlePosition = CGPoint(x: size.width / 2, y: size.height)
+        snowEmitterNode.particlePositionRange = CGVector(dx: size.width, dy: size.height)
+    }
+}

BIN
FreeAPS/Sources/AnimatedBackground/snow.sks


+ 3 - 0
FreeAPS/Sources/Localizations/Main/en.lproj/Localizable.strings

@@ -893,6 +893,9 @@ Enact a temp Basal or a temp target */
 /* */
 "Suspend" = "Suspend";
 
+/* */
+"Animated Background" = "Animated Background";
+
 
 /* Headers for settings ----------------------- */
 "OpenAPS main settings" = "OpenAPS main settings";

+ 3 - 0
FreeAPS/Sources/Localizations/Main/ru.lproj/Localizable.strings

@@ -893,6 +893,9 @@ Enact a temp Basal or a temp target */
 /* */
 "Suspend" = "Остановка";
 
+/* */
+"Animated Background" = "Анимированный фон";
+
 /* Headers for settings ----------------------- */
 "OpenAPS main settings" = "Основные настройки OpenAPS";
 

+ 5 - 0
FreeAPS/Sources/Models/FreeAPSSettings.swift

@@ -22,6 +22,7 @@ struct FreeAPSSettings: JSON, Equatable {
     var lowGlucose: Decimal = 72
     var highGlucose: Decimal = 270
     var carbsRequiredThreshold: Decimal = 10
+    var animatedBackground: Bool = true
 }
 
 extension FreeAPSSettings: Decodable {
@@ -117,6 +118,10 @@ extension FreeAPSSettings: Decodable {
             settings.carbsRequiredThreshold = carbsRequiredThreshold
         }
 
+        if let animatedBackground = try? container.decode(Bool.self, forKey: .animatedBackground) {
+            settings.animatedBackground = animatedBackground
+        }
+
         self = settings
     }
 }

+ 4 - 0
FreeAPS/Sources/Modules/Home/HomeStateModel.swift

@@ -46,6 +46,7 @@ extension Home {
         @Published var units: GlucoseUnits = .mmolL
         @Published var pumpDisplayState: PumpDisplayState?
         @Published var alarm: GlucoseAlarm?
+        @Published var animatedBackground = false
 
         override func subscribe() {
             setupGlucose()
@@ -83,6 +84,8 @@ extension Home {
             broadcaster.register(PumpBatteryObserver.self, observer: self)
             broadcaster.register(PumpReservoirObserver.self, observer: self)
 
+            animatedBackground = settingsManager.settings.animatedBackground
+
             timer.eventHandler = {
                 DispatchQueue.main.async { [weak self] in
                     self?.timerDate = Date()
@@ -348,6 +351,7 @@ extension Home.StateModel:
         allowManualTemp = !settings.closedLoop
         closedLoop = settingsManager.settings.closedLoop
         units = settingsManager.settings.units
+        animatedBackground = settingsManager.settings.animatedBackground
         setupGlucose()
     }
 

+ 139 - 114
FreeAPS/Sources/Modules/Home/View/HomeRootView.swift

@@ -1,3 +1,4 @@
+import SpriteKit
 import SwiftDate
 import SwiftUI
 import Swinject
@@ -29,6 +30,13 @@ extension Home {
             return dateFormatter
         }
 
+        private var spriteScene: SKScene {
+            let scene = SnowScene()
+            scene.scaleMode = .resizeFill
+            scene.backgroundColor = .clear
+            return scene
+        }
+
         var header: some View {
             HStack(alignment: .bottom) {
                 Spacer()
@@ -224,6 +232,101 @@ extension Home {
             .frame(maxWidth: .infinity, maxHeight: 30)
         }
 
+        var mainChart: some View {
+            ZStack {
+                if state.animatedBackground {
+                    SpriteView(scene: spriteScene, options: [.allowsTransparency])
+                        .ignoresSafeArea()
+                        .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
+                }
+
+                MainChartView(
+                    glucose: $state.glucose,
+                    suggestion: $state.suggestion,
+                    tempBasals: $state.tempBasals,
+                    boluses: $state.boluses,
+                    suspensions: $state.suspensions,
+                    hours: .constant(state.filteredHours),
+                    maxBasal: $state.maxBasal,
+                    autotunedBasalProfile: $state.autotunedBasalProfile,
+                    basalProfile: $state.basalProfile,
+                    tempTargets: $state.tempTargets,
+                    carbs: $state.carbs,
+                    timerDate: $state.timerDate,
+                    units: $state.units
+                )
+            }
+            .padding(.bottom)
+            .modal(for: .dataTable, from: self)
+        }
+
+        @ViewBuilder private func bottomPanel(_ geo: GeometryProxy) -> some View {
+            ZStack {
+                Rectangle().fill(Color.gray.opacity(0.2)).frame(height: 50 + geo.safeAreaInsets.bottom)
+
+                HStack {
+                    Button { state.showModal(for: .addCarbs) }
+                    label: {
+                        ZStack(alignment: Alignment(horizontal: .trailing, vertical: .bottom)) {
+                            Image("carbs")
+                                .renderingMode(.template)
+                                .resizable()
+                                .frame(width: 24, height: 24)
+                                .foregroundColor(.loopGreen)
+                                .padding(8)
+                            if let carbsReq = state.carbsRequired {
+                                Text(numberFormatter.string(from: carbsReq as NSNumber)!)
+                                    .font(.caption)
+                                    .foregroundColor(.white)
+                                    .padding(4)
+                                    .background(Capsule().fill(Color.red))
+                            }
+                        }
+                    }
+                    Spacer()
+                    Button { state.showModal(for: .addTempTarget) }
+                    label: {
+                        Image("target")
+                            .renderingMode(.template)
+                            .resizable()
+                            .frame(width: 24, height: 24)
+                            .padding(8)
+                    }.foregroundColor(.loopYellow)
+                    Spacer()
+                    Button { state.showModal(for: .bolus(waitForSuggestion: false)) }
+                    label: {
+                        Image("bolus")
+                            .renderingMode(.template)
+                            .resizable()
+                            .frame(width: 24, height: 24)
+                            .padding(8)
+                    }.foregroundColor(.insulin)
+                    Spacer()
+                    if state.allowManualTemp {
+                        Button { state.showModal(for: .manualTempBasal) }
+                        label: {
+                            Image("bolus1")
+                                .renderingMode(.template)
+                                .resizable()
+                                .frame(width: 24, height: 24)
+                                .padding(8)
+                        }.foregroundColor(.insulin)
+                        Spacer()
+                    }
+                    Button { state.showModal(for: .settings) }
+                    label: {
+                        Image("settings1")
+                            .renderingMode(.template)
+                            .resizable()
+                            .frame(width: 24, height: 24)
+                            .padding(8)
+                    }.foregroundColor(.loopGray)
+                }
+                .padding(.horizontal, 24)
+                .padding(.bottom, geo.safeAreaInsets.bottom)
+            }
+        }
+
         var body: some View {
             GeometryReader { geo in
                 VStack(spacing: 0) {
@@ -233,90 +336,9 @@ extension Home {
                         .background(Color.gray.opacity(0.2))
 
                     infoPanal
-                    MainChartView(
-                        glucose: $state.glucose,
-                        suggestion: $state.suggestion,
-                        tempBasals: $state.tempBasals,
-                        boluses: $state.boluses,
-                        suspensions: $state.suspensions,
-                        hours: .constant(state.filteredHours),
-                        maxBasal: $state.maxBasal,
-                        autotunedBasalProfile: $state.autotunedBasalProfile,
-                        basalProfile: $state.basalProfile,
-                        tempTargets: $state.tempTargets,
-                        carbs: $state.carbs,
-                        timerDate: $state.timerDate,
-                        units: $state.units
-                    )
-                    .padding(.bottom)
-                    .modal(for: .dataTable, from: self)
-
+                    mainChart
                     legendPanal
-
-                    ZStack {
-                        Rectangle().fill(Color.gray.opacity(0.2)).frame(height: 50 + geo.safeAreaInsets.bottom)
-
-                        HStack {
-                            Button { state.showModal(for: .addCarbs) }
-                            label: {
-                                ZStack(alignment: Alignment(horizontal: .trailing, vertical: .bottom)) {
-                                    Image("carbs")
-                                        .renderingMode(.template)
-                                        .resizable()
-                                        .frame(width: 24, height: 24)
-                                        .foregroundColor(.loopGreen)
-                                        .padding(8)
-                                    if let carbsReq = state.carbsRequired {
-                                        Text(numberFormatter.string(from: carbsReq as NSNumber)!)
-                                            .font(.caption)
-                                            .foregroundColor(.white)
-                                            .padding(4)
-                                            .background(Capsule().fill(Color.red))
-                                    }
-                                }
-                            }
-                            Spacer()
-                            Button { state.showModal(for: .addTempTarget) }
-                            label: {
-                                Image("target")
-                                    .renderingMode(.template)
-                                    .resizable()
-                                    .frame(width: 24, height: 24)
-                                    .padding(8)
-                            }.foregroundColor(.loopYellow)
-                            Spacer()
-                            Button { state.showModal(for: .bolus(waitForSuggestion: false)) }
-                            label: {
-                                Image("bolus")
-                                    .renderingMode(.template)
-                                    .resizable()
-                                    .frame(width: 24, height: 24)
-                                    .padding(8)
-                            }.foregroundColor(.insulin)
-                            Spacer()
-                            if state.allowManualTemp {
-                                Button { state.showModal(for: .manualTempBasal) }
-                                label: {
-                                    Image("bolus1")
-                                        .renderingMode(.template)
-                                        .resizable()
-                                        .frame(width: 24, height: 24)
-                                        .padding(8)
-                                }.foregroundColor(.insulin)
-                                Spacer()
-                            }
-                            Button { state.showModal(for: .settings) }
-                            label: {
-                                Image("settings1")
-                                    .renderingMode(.template)
-                                    .resizable()
-                                    .frame(width: 24, height: 24)
-                                    .padding(8)
-                            }.foregroundColor(.loopGray)
-                        }
-                        .padding(.horizontal, 24)
-                        .padding(.bottom, geo.safeAreaInsets.bottom)
-                    }
+                    bottomPanel(geo)
                 }
                 .edgesIgnoringSafeArea(.vertical)
             }
@@ -325,42 +347,45 @@ extension Home {
             .navigationBarHidden(true)
             .ignoresSafeArea(.keyboard)
             .popup(isPresented: isStatusPopupPresented, alignment: .top, direction: .top) {
-                VStack(alignment: .leading, spacing: 4) {
-                    Text(state.statusTitle).font(.headline).foregroundColor(.white)
-                        .padding(.bottom, 4)
-                    if let suggestion = state.suggestion {
-                        TagCloudView(tags: suggestion.reasonParts).animation(.none, value: false)
-                        Text(suggestion.reasonConclusion.capitalizingFirstLetter()).font(.body).foregroundColor(.white)
-                    } else {
-                        Text("No sugestion found").font(.body).foregroundColor(.white)
+                popup
+                    .padding()
+                    .background(
+                        RoundedRectangle(cornerRadius: 8, style: .continuous)
+                            .fill(Color(UIColor.darkGray))
+                    )
+                    .onTapGesture {
+                        isStatusPopupPresented = false
                     }
+                    .gesture(
+                        DragGesture(minimumDistance: 10, coordinateSpace: .local)
+                            .onEnded { value in
+                                if value.translation.height < 0 {
+                                    isStatusPopupPresented = false
+                                }
+                            }
+                    )
+            }
+        }
 
-                    if let errorMessage = state.errorMessage, let date = state.errorDate {
-                        Text("Error at \(dateFormatter.string(from: date))")
-                            .foregroundColor(.white)
-                            .font(.headline)
-                            .padding(.bottom, 4)
-                            .padding(.top, 8)
-                        Text(errorMessage).font(.caption).foregroundColor(.loopRed)
-                    }
+        private var popup: some View {
+            VStack(alignment: .leading, spacing: 4) {
+                Text(state.statusTitle).font(.headline).foregroundColor(.white)
+                    .padding(.bottom, 4)
+                if let suggestion = state.suggestion {
+                    TagCloudView(tags: suggestion.reasonParts).animation(.none, value: false)
+                    Text(suggestion.reasonConclusion.capitalizingFirstLetter()).font(.body).foregroundColor(.white)
+                } else {
+                    Text("No sugestion found").font(.body).foregroundColor(.white)
                 }
-                .padding()
 
-                .background(
-                    RoundedRectangle(cornerRadius: 8, style: .continuous)
-                        .fill(Color(UIColor.darkGray))
-                )
-                .onTapGesture {
-                    isStatusPopupPresented = false
+                if let errorMessage = state.errorMessage, let date = state.errorDate {
+                    Text("Error at \(dateFormatter.string(from: date))")
+                        .foregroundColor(.white)
+                        .font(.headline)
+                        .padding(.bottom, 4)
+                        .padding(.top, 8)
+                    Text(errorMessage).font(.caption).foregroundColor(.loopRed)
                 }
-                .gesture(
-                    DragGesture(minimumDistance: 10, coordinateSpace: .local)
-                        .onEnded { value in
-                            if value.translation.height < 0 {
-                                isStatusPopupPresented = false
-                            }
-                        }
-                )
             }
         }
     }

+ 4 - 1
FreeAPS/Sources/Modules/Settings/SettingsStateModel.swift

@@ -4,9 +4,10 @@ extension Settings {
     final class StateModel: BaseStateModel<Provider> {
         @Injected() private var broadcaster: Broadcaster!
         @Injected() private var fileManager: FileManager!
-        @Published var closedLoop = false
 
+        @Published var closedLoop = false
         @Published var debugOptions = false
+        @Published var animatedBackground = false
 
         private(set) var buildNumber = ""
 
@@ -17,6 +18,8 @@ extension Settings {
             broadcaster.register(SettingsObserver.self, observer: self)
 
             buildNumber = Bundle.main.infoDictionary?["CFBundleVersion"] as? String ?? "Unknown"
+
+            subscribeSetting(\.animatedBackground, on: $animatedBackground) { animatedBackground = $0 }
         }
 
         func logItems() -> [URL] {

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

@@ -102,6 +102,10 @@ extension Settings {
                 }
 
                 Section {
+                    Toggle("Animated Background", isOn: $state.animatedBackground)
+                }
+
+                Section {
                     Text("Share logs")
                         .onTapGesture {
                             showShareSheet = true