Skip to content

Commit

Permalink
ContactTrick
Browse files Browse the repository at this point in the history
  • Loading branch information
mountrcg committed May 8, 2024
2 parents 862ba1d + 67c242a commit c2a90d3
Show file tree
Hide file tree
Showing 18 changed files with 1,896 additions and 0 deletions.
64 changes: 64 additions & 0 deletions FreeAPS.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,16 @@
E4984C5262A90469788754BB /* PreferencesEditorProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F8BA8533F56BC55748CA877 /* PreferencesEditorProvider.swift */; };
E97285ED9B814CD5253C6658 /* AddCarbsDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F48C3AC770D4CCD0EA2B0C2 /* AddCarbsDataFlow.swift */; };
E974172296125A5AE99E634C /* PumpConfigRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AD22C985B79A2F0D2EA3D9D /* PumpConfigRootView.swift */; };
F2159A4A2BA60A6000A0B716 /* ContactTrickDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2159A492BA60A6000A0B716 /* ContactTrickDataFlow.swift */; };
F2159A4C2BA60A8E00A0B716 /* ContactTrickRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2159A4B2BA60A8E00A0B716 /* ContactTrickRootView.swift */; };
F2159A4E2BA60AC000A0B716 /* ContactTrickProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2159A4D2BA60AC000A0B716 /* ContactTrickProvider.swift */; };
F2159A502BA60AE400A0B716 /* ContactTrickStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2159A4F2BA60AE400A0B716 /* ContactTrickStateModel.swift */; };
F2159A522BA60F7A00A0B716 /* FontWeight.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2159A512BA60F7A00A0B716 /* FontWeight.swift */; };
F2159A542BA6207F00A0B716 /* ContactTrickEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2159A532BA6207F00A0B716 /* ContactTrickEntry.swift */; };
F2159A572BA6239F00A0B716 /* ContactTrickManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2159A562BA6239F00A0B716 /* ContactTrickManager.swift */; };
F2159A592BA78B7400A0B716 /* ContactTrickState.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2159A582BA78B7400A0B716 /* ContactTrickState.swift */; };
F2159A5B2BA7939C00A0B716 /* ContactPicture.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2159A5A2BA7939C00A0B716 /* ContactPicture.swift */; };
F270F68D2BAE374C00F6D8DD /* FontTracking.swift in Sources */ = {isa = PBXBuildFile; fileRef = F270F68C2BAE374C00F6D8DD /* FontTracking.swift */; };
F5CA3DB1F9DC8B05792BBFAA /* CGMDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9B5C0607505A38F256BF99A /* CGMDataFlow.swift */; };
F5F7E6C1B7F098F59EB67EC5 /* TargetsEditorDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA49538D56989D8DA6FCF538 /* TargetsEditorDataFlow.swift */; };
F816825E28DB441200054060 /* HeartBeatManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = F816825D28DB441200054060 /* HeartBeatManager.swift */; };
Expand Down Expand Up @@ -1031,6 +1041,16 @@
E625985B47742D498CB1681A /* NotificationsConfigProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = NotificationsConfigProvider.swift; sourceTree = "<group>"; };
E68CDC1E5C438D1BEAD4CF24 /* LibreConfigStateModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = LibreConfigStateModel.swift; sourceTree = "<group>"; };
E9AAB83FB6C3B41EFD1846A0 /* AddTempTargetRootView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = AddTempTargetRootView.swift; sourceTree = "<group>"; };
F2159A492BA60A6000A0B716 /* ContactTrickDataFlow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactTrickDataFlow.swift; sourceTree = "<group>"; };
F2159A4B2BA60A8E00A0B716 /* ContactTrickRootView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactTrickRootView.swift; sourceTree = "<group>"; };
F2159A4D2BA60AC000A0B716 /* ContactTrickProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactTrickProvider.swift; sourceTree = "<group>"; };
F2159A4F2BA60AE400A0B716 /* ContactTrickStateModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactTrickStateModel.swift; sourceTree = "<group>"; };
F2159A512BA60F7A00A0B716 /* FontWeight.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FontWeight.swift; sourceTree = "<group>"; };
F2159A532BA6207F00A0B716 /* ContactTrickEntry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactTrickEntry.swift; sourceTree = "<group>"; };
F2159A562BA6239F00A0B716 /* ContactTrickManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactTrickManager.swift; sourceTree = "<group>"; };
F2159A582BA78B7400A0B716 /* ContactTrickState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactTrickState.swift; sourceTree = "<group>"; };
F2159A5A2BA7939C00A0B716 /* ContactPicture.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactPicture.swift; sourceTree = "<group>"; };
F270F68C2BAE374C00F6D8DD /* FontTracking.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FontTracking.swift; sourceTree = "<group>"; };
F816825D28DB441200054060 /* HeartBeatManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HeartBeatManager.swift; sourceTree = "<group>"; };
F816825F28DB441800054060 /* BluetoothTransmitter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BluetoothTransmitter.swift; sourceTree = "<group>"; };
F90692A9274B7AAE0037068D /* HealthKitManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HealthKitManager.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1341,6 +1361,7 @@
49CA5A152BDA3815001F0D3A /* KetoProtect */,
49CA5A012BD8E459001F0D3A /* B30 */,
CE1F2B982B011C58002EDCA0 /* AutoISF */,
F2159A472BA60A0300A0B716 /* ContactTrick */,
195D80B22AF696EE00D25097 /* Dynamic */,
BD7DA9A32AE06DBA00601B20 /* BolusCalculatorConfig */,
190EBCC229FF134900BA767D /* StatConfig */,
Expand Down Expand Up @@ -1480,6 +1501,7 @@
3811DE9125C9D88200A708ED /* Services */ = {
isa = PBXGroup;
children = (
F2159A552BA6238D00A0B716 /* ContactTrick */,
6B1A8D2C2B156EC100E76752 /* LiveActivity */,
CEB434E128B8F9BC00B70274 /* Bluetooth */,
F90692A8274B7A980037068D /* HealthKit */,
Expand Down Expand Up @@ -1815,6 +1837,9 @@
CC41E2992B1E1F460070974F /* HistoryLayout.swift */,
19B60B772B5E7E97002F4F74 /* Threshold.swift */,
192424CA2B7A64E70063CBF0 /* NIghtscoutExercise.swift */,
F2159A512BA60F7A00A0B716 /* FontWeight.swift */,
F2159A532BA6207F00A0B716 /* ContactTrickEntry.swift */,
F270F68C2BAE374C00F6D8DD /* FontTracking.swift */,
);
path = Models;
sourceTree = "<group>";
Expand Down Expand Up @@ -2563,6 +2588,35 @@
path = View;
sourceTree = "<group>";
};
F2159A472BA60A0300A0B716 /* ContactTrick */ = {
isa = PBXGroup;
children = (
F2159A482BA60A1600A0B716 /* View */,
F2159A492BA60A6000A0B716 /* ContactTrickDataFlow.swift */,
F2159A4D2BA60AC000A0B716 /* ContactTrickProvider.swift */,
F2159A4F2BA60AE400A0B716 /* ContactTrickStateModel.swift */,
);
path = ContactTrick;
sourceTree = "<group>";
};
F2159A482BA60A1600A0B716 /* View */ = {
isa = PBXGroup;
children = (
F2159A4B2BA60A8E00A0B716 /* ContactTrickRootView.swift */,
);
path = View;
sourceTree = "<group>";
};
F2159A552BA6238D00A0B716 /* ContactTrick */ = {
isa = PBXGroup;
children = (
F2159A562BA6239F00A0B716 /* ContactTrickManager.swift */,
F2159A582BA78B7400A0B716 /* ContactTrickState.swift */,
F2159A5A2BA7939C00A0B716 /* ContactPicture.swift */,
);
path = ContactTrick;
sourceTree = "<group>";
};
F5DE2E6D7B2133BBD3353DC7 /* View */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -2965,6 +3019,7 @@
388E595C25AD948C0019842D /* FreeAPSApp.swift in Sources */,
38FEF3FC2737E53800574A46 /* MainStateModel.swift in Sources */,
CECCB4262BDBDCF7006E41C4 /* carbPresetResult.swift in Sources */,
F2159A542BA6207F00A0B716 /* ContactTrickEntry.swift in Sources */,
38569348270B5DFB0002C50D /* GlucoseSource.swift in Sources */,
CEB434E328B8F9DB00B70274 /* BluetoothStateManager.swift in Sources */,
3811DE4225C9D4A100A708ED /* SettingsDataFlow.swift in Sources */,
Expand Down Expand Up @@ -3072,14 +3127,17 @@
495068BD2BDFF1B20048FF3B /* BaseIntentsRequest.swift in Sources */,
495068BC2BDFF1B20048FF3B /* AppShortcuts.swift in Sources */,
3811DEB025C9D88300A708ED /* BaseKeychain.swift in Sources */,
F2159A5B2BA7939C00A0B716 /* ContactPicture.swift in Sources */,
3811DE4325C9D4A100A708ED /* SettingsProvider.swift in Sources */,
45252C95D220E796FDB3B022 /* ConfigEditorDataFlow.swift in Sources */,
3871F38725ED661C0013ECB5 /* Suggestion.swift in Sources */,
38C4D33A25E9A1ED00D30B77 /* NSObject+AssociatedValues.swift in Sources */,
F2159A572BA6239F00A0B716 /* ContactTrickManager.swift in Sources */,
38DF179027733EAD00B3528F /* SnowScene.swift in Sources */,
38AAF8712600C1B0004AF583 /* MainChartView.swift in Sources */,
195F00482B5C267D00DAC71A /* DescriptionView.swift in Sources */,
19DC677F29CA675700FD9EC4 /* OverrideProfilesDataFlow.swift in Sources */,
F2159A522BA60F7A00A0B716 /* FontWeight.swift in Sources */,
1935364028496F7D001E0B16 /* Oref2_variables.swift in Sources */,
CE2FAD3A297D93F0001A872C /* BloodGlucoseExtensions.swift in Sources */,
38E4453A274E411700EC9A94 /* Disk+[UIImage].swift in Sources */,
Expand Down Expand Up @@ -3118,6 +3176,7 @@
F90692D1274B99B60037068D /* HealthKitProvider.swift in Sources */,
19F95FF729F10FEE00314DDC /* StatStateModel.swift in Sources */,
385CEAC125F2EA52002D6D5B /* Announcement.swift in Sources */,
F270F68D2BAE374C00F6D8DD /* FontTracking.swift in Sources */,
8B759CFCF47B392BB365C251 /* BasalProfileEditorDataFlow.swift in Sources */,
195D80B42AF6973A00D25097 /* DynamicRootView.swift in Sources */,
389442CB25F65F7100FA1F27 /* NightscoutTreatment.swift in Sources */,
Expand Down Expand Up @@ -3173,6 +3232,7 @@
CE7CA3562A064973004BE681 /* StateIntentRequest.swift in Sources */,
E4984C5262A90469788754BB /* PreferencesEditorProvider.swift in Sources */,
DD399FB31EACB9343C944C4C /* PreferencesEditorStateModel.swift in Sources */,
F2159A4E2BA60AC000A0B716 /* ContactTrickProvider.swift in Sources */,
19E1F7EA29D082ED005C8D20 /* IconConfigProvider.swift in Sources */,
44190F0BBA464D74B857D1FB /* PreferencesEditorRootView.swift in Sources */,
E97285ED9B814CD5253C6658 /* AddCarbsDataFlow.swift in Sources */,
Expand All @@ -3192,9 +3252,11 @@
E0D4F80527513ECF00BDF1FE /* HealthKitSample.swift in Sources */,
919DBD08F13BAFB180DF6F47 /* AddTempTargetStateModel.swift in Sources */,
49CA5A1A2BDA3873001F0D3A /* KetoProtectDataFlow.swift in Sources */,
F2159A4A2BA60A6000A0B716 /* ContactTrickDataFlow.swift in Sources */,
8BC2F5A29AD1ED08AC0EE013 /* AddTempTargetRootView.swift in Sources */,
38A00B1F25FC00F7006BC0B0 /* Autotune.swift in Sources */,
38AAF85525FFF846004AF583 /* CurrentGlucoseView.swift in Sources */,
F2159A4C2BA60A8E00A0B716 /* ContactTrickRootView.swift in Sources */,
041D1E995A6AE92E9289DC49 /* BolusDataFlow.swift in Sources */,
23888883D4EA091C88480FF2 /* BolusProvider.swift in Sources */,
38E98A2D25F52DC400C0CED0 /* NSLocking+Extensions.swift in Sources */,
Expand All @@ -3220,6 +3282,7 @@
38E4453B274E411700EC9A94 /* Disk+VolumeInformation.swift in Sources */,
7BCFACB97C821041BA43A114 /* ManualTempBasalRootView.swift in Sources */,
38E44534274E411700EC9A94 /* Disk+InternalHelpers.swift in Sources */,
F2159A592BA78B7400A0B716 /* ContactTrickState.swift in Sources */,
38A00B2325FC2B55006BC0B0 /* LRUCache.swift in Sources */,
3083261C4B268E353F36CD0B /* AutotuneConfigDataFlow.swift in Sources */,
891DECF7BC20968D7F566161 /* AutotuneConfigProvider.swift in Sources */,
Expand All @@ -3235,6 +3298,7 @@
38569349270B5DFB0002C50D /* AppGroupSource.swift in Sources */,
F5CA3DB1F9DC8B05792BBFAA /* CGMDataFlow.swift in Sources */,
BA00D96F7B2FF169A06FB530 /* CGMStateModel.swift in Sources */,
F2159A502BA60AE400A0B716 /* ContactTrickStateModel.swift in Sources */,
61962FCAF8A2D222553AC5A3 /* LibreConfigDataFlow.swift in Sources */,
BD7DA9A52AE06DFC00601B20 /* BolusCalculatorConfigDataFlow.swift in Sources */,
6EADD581738D64431902AC0A /* LibreConfigProvider.swift in Sources */,
Expand Down
6 changes: 6 additions & 0 deletions FreeAPS/Resources/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,12 @@
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
</array>
<key>NSCalendarsFullAccessUsageDescription</key>
<string>To create events with BG reading values, so that they can be viewed on Apple Watch and CarPlay</string>
<key>NSContactsUsageDescription</key>
<string>To update contacts with BG reading values (contact trick)</string>
<key>LSApplicationCategoryType</key>
<string></string>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[]
1 change: 1 addition & 0 deletions FreeAPS/Sources/APS/OpenAPS/Constants.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ extension OpenAPS {
static let carbRatios = "settings/carb_ratios.json"
static let tempTargets = "settings/temptargets.json"
static let model = "settings/model.json"
static let contactTrick = "settings/contact_trick.json"
}

enum Monitor {
Expand Down
1 change: 1 addition & 0 deletions FreeAPS/Sources/Assemblies/ServiceAssembly.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ final class ServiceAssembly: Assembly {
container.register(UserNotificationsManager.self) { r in BaseUserNotificationsManager(resolver: r) }
container.register(WatchManager.self) { r in BaseWatchManager(resolver: r) }
container.register(GarminManager.self) { r in BaseGarminManager(resolver: r) }
container.register(ContactTrickManager.self) { r in BaseContactTrickManager(resolver: r) }

if #available(iOS 16.2, *) {
container.register(LiveActivityBridge.self) { r in
Expand Down
83 changes: 83 additions & 0 deletions FreeAPS/Sources/Models/ContactTrickEntry.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@

struct ContactTrickEntry: JSON, Equatable {
var enabled: Bool = false
var layout: ContactTrickLayout = .single
var ring1: ContactTrickLargeRing = .none
var primary: ContactTrickValue = .glucose
var top: ContactTrickValue = .none
var bottom: ContactTrickValue = .none
var contactId: String? = nil
var displayName: String? = nil
var darkMode: Bool = true
var ringWidth: Int = 7
var ringGap: Int = 2
var fontSize: Int = 100
var fontName: String = "Default Font"
var fontWeight: FontWeight = .medium
var fontTracking: FontTracking = .normal

func isDefaultFont() -> Bool {
fontName == "Default Font"
}
}

protocol ContactTrickObserver {
func basalProfileDidChange(_ entry: [ContactTrickEntry])
}

extension ContactTrickEntry {
private enum CodingKeys: String, CodingKey {
case enabled
case layout
case ring1
case primary
case top
case bottom
case contactId
case displayName
case darkMode
case ringWidth
case ringGap
case fontSize
case fontName
case fontWeight
case fontTracking
}

init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let enabled = try container.decodeIfPresent(Bool.self, forKey: .enabled) ?? false
let layout = try container.decodeIfPresent(ContactTrickLayout.self, forKey: .layout) ?? .single
let ring1 = try container.decodeIfPresent(ContactTrickLargeRing.self, forKey: .ring1) ?? .none
let primary = try container.decodeIfPresent(ContactTrickValue.self, forKey: .primary) ?? .glucose
let top = try container.decodeIfPresent(ContactTrickValue.self, forKey: .top) ?? .none
let bottom = try container.decodeIfPresent(ContactTrickValue.self, forKey: .bottom) ?? .none
let contactId = try container.decodeIfPresent(String.self, forKey: .contactId)
let displayName = try container.decodeIfPresent(String.self, forKey: .displayName)
let darkMode = try container.decodeIfPresent(Bool.self, forKey: .darkMode) ?? true
let ringWidth = try container.decodeIfPresent(Int.self, forKey: .ringWidth) ?? 7
let ringGap = try container.decodeIfPresent(Int.self, forKey: .ringGap) ?? 2
let fontSize = try container.decodeIfPresent(Int.self, forKey: .fontSize) ?? 100
let fontName = try container.decodeIfPresent(String.self, forKey: .fontName) ?? "Default Font"
let fontWeight = try container.decodeIfPresent(FontWeight.self, forKey: .fontWeight) ?? .regular
let fontTracking = try container.decodeIfPresent(FontTracking.self, forKey: .fontTracking) ?? .normal

self = ContactTrickEntry(
enabled: enabled,
layout: layout,
ring1: ring1,
primary: primary,
top: top,
bottom: bottom,
contactId: contactId,
displayName: displayName,
darkMode: darkMode,
ringWidth: ringWidth,
ringGap: ringGap,
fontSize: fontSize,
fontName: fontName,
fontWeight: fontWeight,
fontTracking: fontTracking
)
}
}
32 changes: 32 additions & 0 deletions FreeAPS/Sources/Models/FontTracking.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import Foundation

enum FontTracking: String, JSON, Identifiable, CaseIterable, Codable {
var id: String { rawValue }

case tighter
case tight
case normal
case wide

var displayName: String {
switch self {
case .tighter:
NSLocalizedString("Tighter", comment: "")
case .tight:
NSLocalizedString("Tight", comment: "")
case .normal:
NSLocalizedString("Normal", comment: "")
case .wide:
NSLocalizedString("Wide", comment: "")
}
}

var value: Double {
switch self {
case .tighter: -0.05
case .tight: -0.025
case .normal: 0
case .wide: 0.05
}
}
}
29 changes: 29 additions & 0 deletions FreeAPS/Sources/Models/FontWeight.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import Foundation

enum FontWeight: String, JSON, Identifiable, CaseIterable, Codable {
var id: String { rawValue }

case light
case regular
case medium
case semibold
case bold
case black

var displayName: String {
switch self {
case .light:
return NSLocalizedString("Light", comment: "")
case .regular:
return NSLocalizedString("Regular", comment: "")
case .medium:
return NSLocalizedString("Medium", comment: "")
case .semibold:
return NSLocalizedString("Semibold", comment: "")
case .bold:
return NSLocalizedString("Bold", comment: "")
case .black:
return NSLocalizedString("Black", comment: "")
}
}
}
1 change: 1 addition & 0 deletions FreeAPS/Sources/Modules/Base/BaseProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ class BaseProvider: Provider, Injectable {
@Injected() var deviceManager: DeviceDataManager!
@Injected() var storage: FileStorage!
@Injected() var bluetoothProvider: BluetoothStateManager!
@Injected() var contactTrickManager: ContactTrickManager!

required init(resolver: Resolver) {
injectServices(resolver)
Expand Down
30 changes: 30 additions & 0 deletions FreeAPS/Sources/Modules/ContactTrick/ContactTrickDataFlow.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import Combine
import Foundation

enum ContactTrick {
enum Config {}

class Item: Identifiable, Hashable, Equatable {
let id = UUID()
var index: Int = 0
var entry: ContactTrickEntry

init(index: Int, entry: ContactTrickEntry) {
self.index = index
self.entry = entry
}

static func == (lhs: Item, rhs: Item) -> Bool {
lhs.index == rhs.index
}

func hash(into hasher: inout Hasher) {
hasher.combine(index)
}
}
}

protocol ContactTrickProvider: Provider {
var contacts: [ContactTrickEntry] { get }
func saveContacts(_ contacts: [ContactTrickEntry]) -> AnyPublisher<Void, Error>
}
Loading

0 comments on commit c2a90d3

Please sign in to comment.