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