Marc R Kellerman hai 1 ano
pai
achega
01b52846dd
Modificáronse 41 ficheiros con 5530 adicións e 27 borrados
  1. 64 0
      FreeAPS.xcodeproj/project.pbxproj
  2. 3 0
      FreeAPS/Resources/Base.lproj/InfoPlist.strings
  3. 2 0
      FreeAPS/Resources/Info.plist
  4. 1 0
      FreeAPS/Sources/APS/OpenAPS/Constants.swift
  5. 1 0
      FreeAPS/Sources/Assemblies/ServiceAssembly.swift
  6. 152 1
      FreeAPS/Sources/Localizations/Main/ca.lproj/Localizable.strings
  7. 152 1
      FreeAPS/Sources/Localizations/Main/da.lproj/Localizable.strings
  8. 152 1
      FreeAPS/Sources/Localizations/Main/de.lproj/Localizable.strings
  9. 152 1
      FreeAPS/Sources/Localizations/Main/en.lproj/Localizable.strings
  10. 152 1
      FreeAPS/Sources/Localizations/Main/es.lproj/Localizable.strings
  11. 152 1
      FreeAPS/Sources/Localizations/Main/fi.lproj/Localizable.strings
  12. 152 1
      FreeAPS/Sources/Localizations/Main/fr.lproj/Localizable.strings
  13. 152 1
      FreeAPS/Sources/Localizations/Main/he.lproj/Localizable.strings
  14. 152 1
      FreeAPS/Sources/Localizations/Main/hu.lproj/Localizable.strings
  15. 152 1
      FreeAPS/Sources/Localizations/Main/it.lproj/Localizable.strings
  16. 152 1
      FreeAPS/Sources/Localizations/Main/nb.lproj/Localizable.strings
  17. 152 1
      FreeAPS/Sources/Localizations/Main/nl.lproj/Localizable.strings
  18. 152 1
      FreeAPS/Sources/Localizations/Main/pl.lproj/Localizable.strings
  19. 152 1
      FreeAPS/Sources/Localizations/Main/pt-BR.lproj/Localizable.strings
  20. 152 1
      FreeAPS/Sources/Localizations/Main/pt-PT.lproj/Localizable.strings
  21. 152 1
      FreeAPS/Sources/Localizations/Main/ru.lproj/Localizable.strings
  22. 152 1
      FreeAPS/Sources/Localizations/Main/sk.lproj/Localizable.strings
  23. 152 1
      FreeAPS/Sources/Localizations/Main/sv.lproj/Localizable.strings
  24. 152 1
      FreeAPS/Sources/Localizations/Main/tr.lproj/Localizable.strings
  25. 152 1
      FreeAPS/Sources/Localizations/Main/uk.lproj/Localizable.strings
  26. 152 1
      FreeAPS/Sources/Localizations/Main/vi.lproj/Localizable.strings
  27. 152 1
      FreeAPS/Sources/Localizations/Main/zh-Hans.lproj/Localizable.strings
  28. 79 0
      FreeAPS/Sources/Models/ContactTrickEntry.swift
  29. 32 0
      FreeAPS/Sources/Models/FontTracking.swift
  30. 29 0
      FreeAPS/Sources/Models/FontWeight.swift
  31. 1 0
      FreeAPS/Sources/Modules/Base/BaseProvider.swift
  32. 30 0
      FreeAPS/Sources/Modules/ContactTrick/ContactTrickDataFlow.swift
  33. 25 0
      FreeAPS/Sources/Modules/ContactTrick/ContactTrickProvider.swift
  34. 136 0
      FreeAPS/Sources/Modules/ContactTrick/ContactTrickStateModel.swift
  35. 383 0
      FreeAPS/Sources/Modules/ContactTrick/View/ContactTrickRootView.swift
  36. 6 0
      FreeAPS/Sources/Modules/Settings/SettingItems.swift
  37. 10 5
      FreeAPS/Sources/Modules/WatchConfig/View/WatchConfigRootView.swift
  38. 3 0
      FreeAPS/Sources/Router/Screen.swift
  39. 901 0
      FreeAPS/Sources/Services/ContactTrick/ContactPicture.swift
  40. 465 0
      FreeAPS/Sources/Services/ContactTrick/ContactTrickManager.swift
  41. 15 0
      FreeAPS/Sources/Services/ContactTrick/ContactTrickState.swift

+ 64 - 0
FreeAPS.xcodeproj/project.pbxproj

@@ -524,6 +524,16 @@
 		E13B7DAB2A435F57066AF02E /* TargetsEditorStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36F58DDD71F0E795464FA3F0 /* TargetsEditorStateModel.swift */; };
 		E39E418C56A5A46B61D960EE /* ConfigEditorStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D5B4F8B4194BB7E260EF251 /* ConfigEditorStateModel.swift */; };
 		E3A08AAE59538BC8A8ABE477 /* GlucoseNotificationSettingsDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3260468377DA9DB4DEE9AF6D /* GlucoseNotificationSettingsDataFlow.swift */; };
+		E592A3702CEEC01E009A472C /* ContactTrickEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = E592A36F2CEEC01E009A472C /* ContactTrickEntry.swift */; };
+		E592A3772CEEC038009A472C /* ContactTrickStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E592A3752CEEC038009A472C /* ContactTrickStateModel.swift */; };
+		E592A3782CEEC038009A472C /* ContactTrickDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = E592A3732CEEC038009A472C /* ContactTrickDataFlow.swift */; };
+		E592A3792CEEC038009A472C /* ContactTrickRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E592A3712CEEC038009A472C /* ContactTrickRootView.swift */; };
+		E592A37A2CEEC038009A472C /* ContactTrickProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = E592A3742CEEC038009A472C /* ContactTrickProvider.swift */; };
+		E592A37F2CEEC046009A472C /* ContactPicture.swift in Sources */ = {isa = PBXBuildFile; fileRef = E592A37D2CEEC046009A472C /* ContactPicture.swift */; };
+		E592A3802CEEC046009A472C /* ContactTrickManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = E592A37B2CEEC046009A472C /* ContactTrickManager.swift */; };
+		E592A3812CEEC046009A472C /* ContactTrickState.swift in Sources */ = {isa = PBXBuildFile; fileRef = E592A37C2CEEC046009A472C /* ContactTrickState.swift */; };
+		E592A3832CEEC227009A472C /* FontWeight.swift in Sources */ = {isa = PBXBuildFile; fileRef = E592A3822CEEC227009A472C /* FontWeight.swift */; };
+		E592A3852CEEC245009A472C /* FontTracking.swift in Sources */ = {isa = PBXBuildFile; fileRef = E592A3842CEEC245009A472C /* FontTracking.swift */; };
 		E974172296125A5AE99E634C /* PumpConfigRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AD22C985B79A2F0D2EA3D9D /* PumpConfigRootView.swift */; };
 		F5CA3DB1F9DC8B05792BBFAA /* CGMDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9B5C0607505A38F256BF99A /* CGMDataFlow.swift */; };
 		F5F7E6C1B7F098F59EB67EC5 /* TargetsEditorDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA49538D56989D8DA6FCF538 /* TargetsEditorDataFlow.swift */; };
@@ -1201,6 +1211,16 @@
 		E0CC2C5B275B9DAE00A7BC71 /* HealthKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = HealthKit.framework; path = System/Library/Frameworks/HealthKit.framework; sourceTree = SDKROOT; };
 		E0D4F80427513ECF00BDF1FE /* HealthKitSample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HealthKitSample.swift; sourceTree = "<group>"; };
 		E26904AACA8D9C15D229D675 /* SnoozeStateModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = SnoozeStateModel.swift; sourceTree = "<group>"; };
+		E592A36F2CEEC01E009A472C /* ContactTrickEntry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactTrickEntry.swift; sourceTree = "<group>"; };
+		E592A3712CEEC038009A472C /* ContactTrickRootView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactTrickRootView.swift; sourceTree = "<group>"; };
+		E592A3732CEEC038009A472C /* ContactTrickDataFlow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactTrickDataFlow.swift; sourceTree = "<group>"; };
+		E592A3742CEEC038009A472C /* ContactTrickProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactTrickProvider.swift; sourceTree = "<group>"; };
+		E592A3752CEEC038009A472C /* ContactTrickStateModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactTrickStateModel.swift; sourceTree = "<group>"; };
+		E592A37B2CEEC046009A472C /* ContactTrickManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactTrickManager.swift; sourceTree = "<group>"; };
+		E592A37C2CEEC046009A472C /* ContactTrickState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactTrickState.swift; sourceTree = "<group>"; };
+		E592A37D2CEEC046009A472C /* ContactPicture.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactPicture.swift; sourceTree = "<group>"; };
+		E592A3822CEEC227009A472C /* FontWeight.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FontWeight.swift; sourceTree = "<group>"; };
+		E592A3842CEEC245009A472C /* FontTracking.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FontTracking.swift; sourceTree = "<group>"; };
 		E625985B47742D498CB1681A /* GlucoseNotificationSettingsProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = GlucoseNotificationSettingsProvider.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>"; };
@@ -1512,6 +1532,7 @@
 				E42231DBF0DBE2B4B92D1B15 /* CarbRatioEditor */,
 				F75CB57ED6971B46F8756083 /* CGM */,
 				0610F7D6D2EC00E3BA1569F0 /* ConfigEditor */,
+				E592A3762CEEC038009A472C /* ContactTrick */,
 				9E56E3626FAD933385101B76 /* DataTable */,
 				195D80B22AF696EE00D25097 /* DynamicSettings */,
 				DD17454C2C55CA0200211FAC /* GeneralSettings */,
@@ -1656,6 +1677,7 @@
 				38E8754D275556E100975559 /* WatchManager */,
 				38E87406274F9AA500975559 /* UserNotifications */,
 				3862CC2C2743F9DC00BF832C /* Calendar */,
+				E592A37E2CEEC046009A472C /* ContactTrick */,
 				38AEE75025F021F10013F05B /* SettingsManager */,
 				38B4F3C425E5016800E76A18 /* Notifications */,
 				3811DE9225C9D88200A708ED /* Appearance */,
@@ -1962,6 +1984,9 @@
 				38A9260425F012D8009E3739 /* CarbRatios.swift */,
 				38D0B3D825EC07C400CB6E88 /* CarbsEntry.swift */,
 				3811DF0125CA9FEA00A708ED /* Credentials.swift */,
+				E592A36F2CEEC01E009A472C /* ContactTrickEntry.swift */,
+				E592A3822CEEC227009A472C /* FontWeight.swift */,
+				E592A3842CEEC245009A472C /* FontTracking.swift */,
 				38AEE73C25F0200C0013F05B /* FreeAPSSettings.swift */,
 				383948D925CD64D500E91849 /* Glucose.swift */,
 				382C133625F13A1E00715CE1 /* InsulinSensitivities.swift */,
@@ -2877,6 +2902,35 @@
 			path = CarbRatioEditor;
 			sourceTree = "<group>";
 		};
+		E592A3722CEEC038009A472C /* View */ = {
+			isa = PBXGroup;
+			children = (
+				E592A3712CEEC038009A472C /* ContactTrickRootView.swift */,
+			);
+			path = View;
+			sourceTree = "<group>";
+		};
+		E592A3762CEEC038009A472C /* ContactTrick */ = {
+			isa = PBXGroup;
+			children = (
+				E592A3722CEEC038009A472C /* View */,
+				E592A3732CEEC038009A472C /* ContactTrickDataFlow.swift */,
+				E592A3742CEEC038009A472C /* ContactTrickProvider.swift */,
+				E592A3752CEEC038009A472C /* ContactTrickStateModel.swift */,
+			);
+			path = ContactTrick;
+			sourceTree = "<group>";
+		};
+		E592A37E2CEEC046009A472C /* ContactTrick */ = {
+			isa = PBXGroup;
+			children = (
+				E592A37B2CEEC046009A472C /* ContactTrickManager.swift */,
+				E592A37C2CEEC046009A472C /* ContactTrickState.swift */,
+				E592A37D2CEEC046009A472C /* ContactPicture.swift */,
+			);
+			path = ContactTrick;
+			sourceTree = "<group>";
+		};
 		EEC747824D6593B5CD87E195 /* View */ = {
 			isa = PBXGroup;
 			children = (
@@ -3295,6 +3349,9 @@
 				38B4F3CA25E502E200E76A18 /* SwiftNotificationCenter.swift in Sources */,
 				38AEE75225F022080013F05B /* SettingsManager.swift in Sources */,
 				3894873A2614928B004DF424 /* DispatchTimer.swift in Sources */,
+				E592A37F2CEEC046009A472C /* ContactPicture.swift in Sources */,
+				E592A3802CEEC046009A472C /* ContactTrickManager.swift in Sources */,
+				E592A3812CEEC046009A472C /* ContactTrickState.swift in Sources */,
 				3895E4C625B9E00D00214B37 /* Preferences.swift in Sources */,
 				CE94598429E9E3E60047C9C6 /* WatchConfigStateModel.swift in Sources */,
 				DD6B7CB92C7BAC6900B75029 /* NightscoutImportResultView.swift in Sources */,
@@ -3325,6 +3382,7 @@
 				E00EEC0527368630002FF094 /* StorageAssembly.swift in Sources */,
 				DD1745552C55CA6C00211FAC /* UnitsLimitsSettingsRootView.swift in Sources */,
 				384E803825C388640086DB71 /* Script.swift in Sources */,
+				E592A3832CEEC227009A472C /* FontWeight.swift in Sources */,
 				CE94597E29E9E1EE0047C9C6 /* GarminManager.swift in Sources */,
 				3883583425EEB38000E024B2 /* PumpSettings.swift in Sources */,
 				38DAB280260CBB7F00F74C1A /* PumpView.swift in Sources */,
@@ -3405,6 +3463,7 @@
 				38B4F3AF25E2979F00E76A18 /* IndexedCollection.swift in Sources */,
 				58D08B222C8DAA8E00AA37D3 /* OverrideView.swift in Sources */,
 				BD0B2EF32C5998E600B3298F /* MealPresetView.swift in Sources */,
+				E592A3702CEEC01E009A472C /* ContactTrickEntry.swift in Sources */,
 				DD6D67E42C9C253500660C9B /* ColorSchemeOption.swift in Sources */,
 				582DF9752C8CDB92001F516D /* GlucoseChartView.swift in Sources */,
 				DD1745302C55AE5300211FAC /* TargetBehaviorProvider.swift in Sources */,
@@ -3619,6 +3678,10 @@
 				38E8755427561E9800975559 /* DataFlow.swift in Sources */,
 				38E44522274E3DDC00EC9A94 /* NetworkReachabilityManager.swift in Sources */,
 				CE7CA34F2A064973004BE681 /* BaseIntentsRequest.swift in Sources */,
+				E592A3772CEEC038009A472C /* ContactTrickStateModel.swift in Sources */,
+				E592A3782CEEC038009A472C /* ContactTrickDataFlow.swift in Sources */,
+				E592A3792CEEC038009A472C /* ContactTrickRootView.swift in Sources */,
+				E592A37A2CEEC038009A472C /* ContactTrickProvider.swift in Sources */,
 				CE82E02728E869DF00473A9C /* AlertEntry.swift in Sources */,
 				38E4451E274DB04600EC9A94 /* AppDelegate.swift in Sources */,
 				BD2FF1A02AE29D43005D1C5D /* CheckboxToggleStyle.swift in Sources */,
@@ -3696,6 +3759,7 @@
 				DDE179562C910127003CDDB7 /* BolusStored+CoreDataClass.swift in Sources */,
 				DDE179572C910127003CDDB7 /* BolusStored+CoreDataProperties.swift in Sources */,
 				DDE179582C910127003CDDB7 /* ForecastValue+CoreDataClass.swift in Sources */,
+				E592A3852CEEC245009A472C /* FontTracking.swift in Sources */,
 				DDE179592C910127003CDDB7 /* ForecastValue+CoreDataProperties.swift in Sources */,
 				DDE1795A2C910127003CDDB7 /* CarbEntryStored+CoreDataClass.swift in Sources */,
 				DDE1795B2C910127003CDDB7 /* CarbEntryStored+CoreDataProperties.swift in Sources */,

+ 3 - 0
FreeAPS/Resources/Base.lproj/InfoPlist.strings

@@ -18,3 +18,6 @@
 
 /* Privacy - Health Share Usage Description */
 "NSHealthShareUsageDescription" = "Health App is used to store blood glucose, insulin and carbohydrates";
+
+/* Privacy - Contacts Usage Description */
+"NSContactsUsageDescription" = "To enable the Contact Image feature: get live updates from Trio to your Apple Watch Contact complication";

+ 2 - 0
FreeAPS/Resources/Info.plist

@@ -78,6 +78,8 @@
 	<string>Calendar is used to create a new glucose events.</string>
 	<key>NSFaceIDUsageDescription</key>
 	<string>For authorized acces to bolus</string>
+	<key>NSContactsUsageDescription</key>
+	<string>Contact is used to create a Apple Watch complication</string>
 	<key>NSHealthShareUsageDescription</key>
 	<string>Health App is used to store blood glucose, carbs and insulin</string>
 	<key>NSHealthUpdateUsageDescription</key>

+ 1 - 0
FreeAPS/Sources/APS/OpenAPS/Constants.swift

@@ -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 {

+ 1 - 0
FreeAPS/Sources/Assemblies/ServiceAssembly.swift

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

+ 152 - 1
FreeAPS/Sources/Localizations/Main/ca.lproj/Localizable.strings

@@ -1600,8 +1600,159 @@ Enact a temp Basal or a temp target */
 /* Smoothing of CGM readings */
 "Smooth Glucose Value" = "Smooth Glucose Value";
 
-/* -----------------------------------------------------------------------------------------------------------
+/* --------------------------------------------------------  Contact Image --------------------------------------*/
 
+/* Contact Image, contacts list - prefix before the contact given name */
+"Contact" = "Contact";
+
+/* Contact Image, settings, header of the contacts list */
+"Contacts" = "Contacts";
+
+/* Contact Image, settings, save your changes reminedr */
+"Don't forget to save your changes." = "Don't forget to save your changes.";
+
+/* Contact Image, settings, message about need to access contacts */
+"Trio needs access to your contacts for this feature to work" = "Trio needs access to your contacts for this feature to work";
+
+/* Contact Image, settings, button to grant access to contacts */
+"Grant Trio access to contacts" = "Grant Trio access to contacts";
+
+/* Contact Image, settings, message when access to contacts denied */
+"Access to contacts denied" = "Access to contacts denied";
+
+/* Contact Image, settings, message when access to contacts restricted */
+"Access to contacts is restricted (parental control?)" = "Access to contacts is restricted (parental control?)";
+
+/* Contact Image, settings, message when access to contacts is in unknown state */
+"Access to contacts - unknown state" = "Access to contacts - unknown state";
+
+/* Contact Image, settings, short description of the feature */
+"A Contact Image can be used to get live updates from Trio to your Apple Watch Contact complication and/or your iPhone Contact widget." = "A Contact Image can be used to get live updates from Trio to your Apple Watch Contact complication and/or your iPhone Contact widget.";
+
+/* Contact Image, settings, main header */
+"Contact Image" = "Contact Image";
+
+/* Contact Image, settings, label for the layout picker */
+"Layout" = "Layout";
+
+/* Contact Image, settings, single layout name */
+"Single" = "Single";
+
+/* Contact Image, settings, split layout name */
+"Split" = "Split";
+
+/* Contact Image, settings, outer ring - don't show */
+"DontShowRing" = "Don't show";
+
+/* Contact Image, settings, outer ring - Loop status */
+"LoopStatusRing" = "Loop status";
+
+/* Contact Image, settings, outer ring - IOB */
+"IOBRing" = "IOB";
+
+/* Contact Image, settings, outer ring - COB */
+"COBRing" = "COB";
+
+/* Contact Image, settings, outer ring - IOB+COB */
+"IOB+COBRing" = "IOB+COB";
+
+/* Contact Image, settings, label for the primary value picker */
+"Primary" = "Primary";
+
+/* Contact Image, settings, label for the top value picker */
+"Top" = "Top";
+
+/* Contact Image, settings, label for the bottom value picker */
+"Bottom" = "Bottom";
+
+/* Contact Image, settings, header for the outer ring section */
+"Ring" = "Ring";
+
+/* Contact Image, settings, label for the outer ring picker */
+"Outer" = "Outer";
+
+/* Contact Image, settings, label for the ring width picker */
+"Width" = "Width";
+
+/* Contact Image, settings, label for the ring gap picker */
+"Gap" = "Gap";
+
+/* Contact Image, settings, header for the font section */
+"Font" = "Font";
+
+/* Contact Image, settings, label for the font size picker */
+"Size" = "Size";
+
+/* Contact Image, settings, label for the secondary font size picker */
+"Secondary size" = "Secondary size";
+
+/* Contact Image, settings, label for the font tracking picker (tracking - horizontal spacing between letters) */
+"Tracking" = "Tracking";
+
+/* Contact Image, settings, label for the font weight picker */
+"Weight" = "Weight";
+
+/* Contact Image, settings, label for the dark-mode switcher */
+"Dark mode" = "Dark mode";
+
+/* Contact Image, settings, tighter font tracking */
+"TighterFontTracking" = "Tighter";
+
+/* Contact Image, settings, tight font tracking */
+"TightFontTracking" = "Tight";
+
+/* Contact Image, settings, normal font tracking */
+"NormalFontTracking" = "Normal";
+
+/* Contact Image, settings, wide font tracking */
+"WideFontTracking" = "Wide";
+
+/* Contact Image, settings, light font weight */
+"LightFontWeight" = "Light";
+
+/* Contact Image, settings, regular font weight */
+"RegularFontWeight" = "Regular";
+
+/* Contact Image, settings, medium font weight */
+"MediumFontWeight" = "Medium";
+
+/* Contact Image, settings, semibold font weight */
+"SemiboldFontWeight" = "Semibold";
+
+/* Contact Image, settings, bold font weight */
+"BoldFontWeight" = "Bold";
+
+/* Contact Image, settings, black font weight (black is "very bold") */
+"BlackFontWeight" = "Black";
+
+/* Contact Image, settings, don't display any value */
+"NoneContactValue" = "None";
+
+/* Contact Image, settings, Glucose (data to display) */
+"GlucoseContactValue" = "Glucose";
+
+/* Contact Image, settings, Eventual BG (data to display) */
+"EventualBGContactValue" = "Eventual BG";
+
+/* Contact Image, settings, Delta (data to display) */
+"DeltaContactValue" = "Delta";
+
+/* Contact Image, settings, Trend (data to display) */
+"TrendContactValue" = "Trend";
+
+/* Contact Image, settings, Last loop time (data to display) */
+"LastLoopTimeContactValue" = "Last loop time";
+
+/* Contact Image, settings, COB (data to display) */
+"COBContactValue" = "COB";
+
+/* Contact Image, settings, IOB (data to display) */
+"IOBContactValue" = "IOB";
+
+/* Contact Image, settings, loop status (data to display) */
+"LoopStatusContactValue" = "Loop status";
+
+/* ---------------------------------------------------------------------------------------------------------------
   Infotexts from openaps.docs and androidaps.docs
   Trio
 */

+ 152 - 1
FreeAPS/Sources/Localizations/Main/da.lproj/Localizable.strings

@@ -1813,8 +1813,159 @@ Enact a temp Basal or a temp target */
 /* Smoothing of CGM readings */
 "Smooth Glucose Value" = "Smooth Glucose Value";
 
-/* -----------------------------------------------------------------------------------------------------------
+/* --------------------------------------------------------  Contact Image --------------------------------------*/
 
+/* Contact Image, contacts list - prefix before the contact given name */
+"Contact" = "Contact";
+
+/* Contact Image, settings, header of the contacts list */
+"Contacts" = "Contacts";
+
+/* Contact Image, settings, save your changes reminedr */
+"Don't forget to save your changes." = "Don't forget to save your changes.";
+
+/* Contact Image, settings, message about need to access contacts */
+"Trio needs access to your contacts for this feature to work" = "Trio needs access to your contacts for this feature to work";
+
+/* Contact Image, settings, button to grant access to contacts */
+"Grant Trio access to contacts" = "Grant Trio access to contacts";
+
+/* Contact Image, settings, message when access to contacts denied */
+"Access to contacts denied" = "Access to contacts denied";
+
+/* Contact Image, settings, message when access to contacts restricted */
+"Access to contacts is restricted (parental control?)" = "Access to contacts is restricted (parental control?)";
+
+/* Contact Image, settings, message when access to contacts is in unknown state */
+"Access to contacts - unknown state" = "Access to contacts - unknown state";
+
+/* Contact Image, settings, short description of the feature */
+"A Contact Image can be used to get live updates from Trio to your Apple Watch Contact complication and/or your iPhone Contact widget." = "A Contact Image can be used to get live updates from Trio to your Apple Watch Contact complication and/or your iPhone Contact widget.";
+
+/* Contact Image, settings, main header */
+"Contact Image" = "Contact Image";
+
+/* Contact Image, settings, label for the layout picker */
+"Layout" = "Layout";
+
+/* Contact Image, settings, single layout name */
+"Single" = "Single";
+
+/* Contact Image, settings, split layout name */
+"Split" = "Split";
+
+/* Contact Image, settings, outer ring - don't show */
+"DontShowRing" = "Don't show";
+
+/* Contact Image, settings, outer ring - Loop status */
+"LoopStatusRing" = "Loop status";
+
+/* Contact Image, settings, outer ring - IOB */
+"IOBRing" = "IOB";
+
+/* Contact Image, settings, outer ring - COB */
+"COBRing" = "COB";
+
+/* Contact Image, settings, outer ring - IOB+COB */
+"IOB+COBRing" = "IOB+COB";
+
+/* Contact Image, settings, label for the primary value picker */
+"Primary" = "Primary";
+
+/* Contact Image, settings, label for the top value picker */
+"Top" = "Top";
+
+/* Contact Image, settings, label for the bottom value picker */
+"Bottom" = "Bottom";
+
+/* Contact Image, settings, header for the outer ring section */
+"Ring" = "Ring";
+
+/* Contact Image, settings, label for the outer ring picker */
+"Outer" = "Outer";
+
+/* Contact Image, settings, label for the ring width picker */
+"Width" = "Width";
+
+/* Contact Image, settings, label for the ring gap picker */
+"Gap" = "Gap";
+
+/* Contact Image, settings, header for the font section */
+"Font" = "Font";
+
+/* Contact Image, settings, label for the font size picker */
+"Size" = "Size";
+
+/* Contact Image, settings, label for the secondary font size picker */
+"Secondary size" = "Secondary size";
+
+/* Contact Image, settings, label for the font tracking picker (tracking - horizontal spacing between letters) */
+"Tracking" = "Tracking";
+
+/* Contact Image, settings, label for the font weight picker */
+"Weight" = "Weight";
+
+/* Contact Image, settings, label for the dark-mode switcher */
+"Dark mode" = "Dark mode";
+
+/* Contact Image, settings, tighter font tracking */
+"TighterFontTracking" = "Tighter";
+
+/* Contact Image, settings, tight font tracking */
+"TightFontTracking" = "Tight";
+
+/* Contact Image, settings, normal font tracking */
+"NormalFontTracking" = "Normal";
+
+/* Contact Image, settings, wide font tracking */
+"WideFontTracking" = "Wide";
+
+/* Contact Image, settings, light font weight */
+"LightFontWeight" = "Light";
+
+/* Contact Image, settings, regular font weight */
+"RegularFontWeight" = "Regular";
+
+/* Contact Image, settings, medium font weight */
+"MediumFontWeight" = "Medium";
+
+/* Contact Image, settings, semibold font weight */
+"SemiboldFontWeight" = "Semibold";
+
+/* Contact Image, settings, bold font weight */
+"BoldFontWeight" = "Bold";
+
+/* Contact Image, settings, black font weight (black is "very bold") */
+"BlackFontWeight" = "Black";
+
+/* Contact Image, settings, don't display any value */
+"NoneContactValue" = "None";
+
+/* Contact Image, settings, Glucose (data to display) */
+"GlucoseContactValue" = "Glucose";
+
+/* Contact Image, settings, Eventual BG (data to display) */
+"EventualBGContactValue" = "Eventual BG";
+
+/* Contact Image, settings, Delta (data to display) */
+"DeltaContactValue" = "Delta";
+
+/* Contact Image, settings, Trend (data to display) */
+"TrendContactValue" = "Trend";
+
+/* Contact Image, settings, Last loop time (data to display) */
+"LastLoopTimeContactValue" = "Last loop time";
+
+/* Contact Image, settings, COB (data to display) */
+"COBContactValue" = "COB";
+
+/* Contact Image, settings, IOB (data to display) */
+"IOBContactValue" = "IOB";
+
+/* Contact Image, settings, loop status (data to display) */
+"LoopStatusContactValue" = "Loop status";
+
+/* ---------------------------------------------------------------------------------------------------------------
   Infotexts from openaps.docs and androidaps.docs
   Trio
 */

+ 152 - 1
FreeAPS/Sources/Localizations/Main/de.lproj/Localizable.strings

@@ -1858,8 +1858,159 @@ Enact a temp Basal or a temp target */
 /* Smoothing of CGM readings */
 "Smooth Glucose Value" = "Glätten der Glukosewerte";
 
-/* -----------------------------------------------------------------------------------------------------------
+/* --------------------------------------------------------  Contact Image --------------------------------------*/
 
+/* Contact Image, contacts list - prefix before the contact given name */
+"Contact" = "Contact";
+
+/* Contact Image, settings, header of the contacts list */
+"Contacts" = "Contacts";
+
+/* Contact Image, settings, save your changes reminedr */
+"Don't forget to save your changes." = "Don't forget to save your changes.";
+
+/* Contact Image, settings, message about need to access contacts */
+"Trio needs access to your contacts for this feature to work" = "Trio needs access to your contacts for this feature to work";
+
+/* Contact Image, settings, button to grant access to contacts */
+"Grant Trio access to contacts" = "Grant Trio access to contacts";
+
+/* Contact Image, settings, message when access to contacts denied */
+"Access to contacts denied" = "Access to contacts denied";
+
+/* Contact Image, settings, message when access to contacts restricted */
+"Access to contacts is restricted (parental control?)" = "Access to contacts is restricted (parental control?)";
+
+/* Contact Image, settings, message when access to contacts is in unknown state */
+"Access to contacts - unknown state" = "Access to contacts - unknown state";
+
+/* Contact Image, settings, short description of the feature */
+"A Contact Image can be used to get live updates from Trio to your Apple Watch Contact complication and/or your iPhone Contact widget." = "A Contact Image can be used to get live updates from Trio to your Apple Watch Contact complication and/or your iPhone Contact widget.";
+
+/* Contact Image, settings, main header */
+"Contact Image" = "Contact Image";
+
+/* Contact Image, settings, label for the layout picker */
+"Layout" = "Layout";
+
+/* Contact Image, settings, single layout name */
+"Single" = "Single";
+
+/* Contact Image, settings, split layout name */
+"Split" = "Split";
+
+/* Contact Image, settings, outer ring - don't show */
+"DontShowRing" = "Don't show";
+
+/* Contact Image, settings, outer ring - Loop status */
+"LoopStatusRing" = "Loop status";
+
+/* Contact Image, settings, outer ring - IOB */
+"IOBRing" = "IOB";
+
+/* Contact Image, settings, outer ring - COB */
+"COBRing" = "COB";
+
+/* Contact Image, settings, outer ring - IOB+COB */
+"IOB+COBRing" = "IOB+COB";
+
+/* Contact Image, settings, label for the primary value picker */
+"Primary" = "Primary";
+
+/* Contact Image, settings, label for the top value picker */
+"Top" = "Top";
+
+/* Contact Image, settings, label for the bottom value picker */
+"Bottom" = "Bottom";
+
+/* Contact Image, settings, header for the outer ring section */
+"Ring" = "Ring";
+
+/* Contact Image, settings, label for the outer ring picker */
+"Outer" = "Outer";
+
+/* Contact Image, settings, label for the ring width picker */
+"Width" = "Width";
+
+/* Contact Image, settings, label for the ring gap picker */
+"Gap" = "Gap";
+
+/* Contact Image, settings, header for the font section */
+"Font" = "Font";
+
+/* Contact Image, settings, label for the font size picker */
+"Size" = "Size";
+
+/* Contact Image, settings, label for the secondary font size picker */
+"Secondary size" = "Secondary size";
+
+/* Contact Image, settings, label for the font tracking picker (tracking - horizontal spacing between letters) */
+"Tracking" = "Tracking";
+
+/* Contact Image, settings, label for the font weight picker */
+"Weight" = "Weight";
+
+/* Contact Image, settings, label for the dark-mode switcher */
+"Dark mode" = "Dark mode";
+
+/* Contact Image, settings, tighter font tracking */
+"TighterFontTracking" = "Tighter";
+
+/* Contact Image, settings, tight font tracking */
+"TightFontTracking" = "Tight";
+
+/* Contact Image, settings, normal font tracking */
+"NormalFontTracking" = "Normal";
+
+/* Contact Image, settings, wide font tracking */
+"WideFontTracking" = "Wide";
+
+/* Contact Image, settings, light font weight */
+"LightFontWeight" = "Light";
+
+/* Contact Image, settings, regular font weight */
+"RegularFontWeight" = "Regular";
+
+/* Contact Image, settings, medium font weight */
+"MediumFontWeight" = "Medium";
+
+/* Contact Image, settings, semibold font weight */
+"SemiboldFontWeight" = "Semibold";
+
+/* Contact Image, settings, bold font weight */
+"BoldFontWeight" = "Bold";
+
+/* Contact Image, settings, black font weight (black is "very bold") */
+"BlackFontWeight" = "Black";
+
+/* Contact Image, settings, don't display any value */
+"NoneContactValue" = "None";
+
+/* Contact Image, settings, Glucose (data to display) */
+"GlucoseContactValue" = "Glucose";
+
+/* Contact Image, settings, Eventual BG (data to display) */
+"EventualBGContactValue" = "Eventual BG";
+
+/* Contact Image, settings, Delta (data to display) */
+"DeltaContactValue" = "Delta";
+
+/* Contact Image, settings, Trend (data to display) */
+"TrendContactValue" = "Trend";
+
+/* Contact Image, settings, Last loop time (data to display) */
+"LastLoopTimeContactValue" = "Last loop time";
+
+/* Contact Image, settings, COB (data to display) */
+"COBContactValue" = "COB";
+
+/* Contact Image, settings, IOB (data to display) */
+"IOBContactValue" = "IOB";
+
+/* Contact Image, settings, loop status (data to display) */
+"LoopStatusContactValue" = "Loop status";
+
+/* ---------------------------------------------------------------------------------------------------------------
   Infotexts from openaps.docs and androidaps.docs
   Trio
 */

+ 152 - 1
FreeAPS/Sources/Localizations/Main/en.lproj/Localizable.strings

@@ -1859,8 +1859,159 @@ Enact a temp Basal or a temp target */
 /* Smoothing of CGM readings */
 "Smooth Glucose Value" = "Smooth Glucose Value";
 
- /* -----------------------------------------------------------------------------------------------------------
+/* --------------------------------------------------------  Contact Image --------------------------------------*/
 
+/* Contact Image, contacts list - prefix before the contact given name */
+"Contact" = "Contact";
+
+/* Contact Image, settings, header of the contacts list */
+"Contacts" = "Contacts";
+
+/* Contact Image, settings, save your changes reminedr */
+"Don't forget to save your changes." = "Don't forget to save your changes.";
+
+/* Contact Image, settings, message about need to access contacts */
+"Trio needs access to your contacts for this feature to work" = "Trio needs access to your contacts for this feature to work";
+
+/* Contact Image, settings, button to grant access to contacts */
+"Grant Trio access to contacts" = "Grant Trio access to contacts";
+
+/* Contact Image, settings, message when access to contacts denied */
+"Access to contacts denied" = "Access to contacts denied";
+
+/* Contact Image, settings, message when access to contacts restricted */
+"Access to contacts is restricted (parental control?)" = "Access to contacts is restricted (parental control?)";
+
+/* Contact Image, settings, message when access to contacts is in unknown state */
+"Access to contacts - unknown state" = "Access to contacts - unknown state";
+
+/* Contact Image, settings, short description of the feature */
+"A Contact Image can be used to get live updates from Trio to your Apple Watch Contact complication and/or your iPhone Contact widget." = "A Contact Image can be used to get live updates from Trio to your Apple Watch Contact complication and/or your iPhone Contact widget.";
+
+/* Contact Image, settings, main header */
+"Contact Image" = "Contact Image";
+
+/* Contact Image, settings, label for the layout picker */
+"Layout" = "Layout";
+
+/* Contact Image, settings, single layout name */
+"Single" = "Single";
+
+/* Contact Image, settings, split layout name */
+"Split" = "Split";
+
+/* Contact Image, settings, outer ring - don't show */
+"DontShowRing" = "Don't show";
+
+/* Contact Image, settings, outer ring - Loop status */
+"LoopStatusRing" = "Loop status";
+
+/* Contact Image, settings, outer ring - IOB */
+"IOBRing" = "IOB";
+
+/* Contact Image, settings, outer ring - COB */
+"COBRing" = "COB";
+
+/* Contact Image, settings, outer ring - IOB+COB */
+"IOB+COBRing" = "IOB+COB";
+
+/* Contact Image, settings, label for the primary value picker */
+"Primary" = "Primary";
+
+/* Contact Image, settings, label for the top value picker */
+"Top" = "Top";
+
+/* Contact Image, settings, label for the bottom value picker */
+"Bottom" = "Bottom";
+
+/* Contact Image, settings, header for the outer ring section */
+"Ring" = "Ring";
+
+/* Contact Image, settings, label for the outer ring picker */
+"Outer" = "Outer";
+
+/* Contact Image, settings, label for the ring width picker */
+"Width" = "Width";
+
+/* Contact Image, settings, label for the ring gap picker */
+"Gap" = "Gap";
+
+/* Contact Image, settings, header for the font section */
+"Font" = "Font";
+
+/* Contact Image, settings, label for the font size picker */
+"Size" = "Size";
+
+/* Contact Image, settings, label for the secondary font size picker */
+"Secondary size" = "Secondary size";
+
+/* Contact Image, settings, label for the font tracking picker (tracking - horizontal spacing between letters) */
+"Tracking" = "Tracking";
+
+/* Contact Image, settings, label for the font weight picker */
+"Weight" = "Weight";
+
+/* Contact Image, settings, label for the dark-mode switcher */
+"Dark mode" = "Dark mode";
+
+/* Contact Image, settings, tighter font tracking */
+"TighterFontTracking" = "Tighter";
+
+/* Contact Image, settings, tight font tracking */
+"TightFontTracking" = "Tight";
+
+/* Contact Image, settings, normal font tracking */
+"NormalFontTracking" = "Normal";
+
+/* Contact Image, settings, wide font tracking */
+"WideFontTracking" = "Wide";
+
+/* Contact Image, settings, light font weight */
+"LightFontWeight" = "Light";
+
+/* Contact Image, settings, regular font weight */
+"RegularFontWeight" = "Regular";
+
+/* Contact Image, settings, medium font weight */
+"MediumFontWeight" = "Medium";
+
+/* Contact Image, settings, semibold font weight */
+"SemiboldFontWeight" = "Semibold";
+
+/* Contact Image, settings, bold font weight */
+"BoldFontWeight" = "Bold";
+
+/* Contact Image, settings, black font weight (black is "very bold") */
+"BlackFontWeight" = "Black";
+
+/* Contact Image, settings, don't display any value */
+"NoneContactValue" = "None";
+
+/* Contact Image, settings, Glucose (data to display) */
+"GlucoseContactValue" = "Glucose";
+
+/* Contact Image, settings, Eventual BG (data to display) */
+"EventualBGContactValue" = "Eventual BG";
+
+/* Contact Image, settings, Delta (data to display) */
+"DeltaContactValue" = "Delta";
+
+/* Contact Image, settings, Trend (data to display) */
+"TrendContactValue" = "Trend";
+
+/* Contact Image, settings, Last loop time (data to display) */
+"LastLoopTimeContactValue" = "Last loop time";
+
+/* Contact Image, settings, COB (data to display) */
+"COBContactValue" = "COB";
+
+/* Contact Image, settings, IOB (data to display) */
+"IOBContactValue" = "IOB";
+
+/* Contact Image, settings, loop status (data to display) */
+"LoopStatusContactValue" = "Loop status";
+
+/* ---------------------------------------------------------------------------------------------------------------
   Infotexts from openaps.docs and androidaps.docs
   Trio
 */

+ 152 - 1
FreeAPS/Sources/Localizations/Main/es.lproj/Localizable.strings

@@ -1851,8 +1851,159 @@ Enact a temp Basal or a temp target */
 /* Smoothing of CGM readings */
 "Smooth Glucose Value" = "Smooth Glucose Value";
 
-/* -----------------------------------------------------------------------------------------------------------
+/* --------------------------------------------------------  Contact Image --------------------------------------*/
 
+/* Contact Image, contacts list - prefix before the contact given name */
+"Contact" = "Contact";
+
+/* Contact Image, settings, header of the contacts list */
+"Contacts" = "Contacts";
+
+/* Contact Image, settings, save your changes reminedr */
+"Don't forget to save your changes." = "Don't forget to save your changes.";
+
+/* Contact Image, settings, message about need to access contacts */
+"Trio needs access to your contacts for this feature to work" = "Trio needs access to your contacts for this feature to work";
+
+/* Contact Image, settings, button to grant access to contacts */
+"Grant Trio access to contacts" = "Grant Trio access to contacts";
+
+/* Contact Image, settings, message when access to contacts denied */
+"Access to contacts denied" = "Access to contacts denied";
+
+/* Contact Image, settings, message when access to contacts restricted */
+"Access to contacts is restricted (parental control?)" = "Access to contacts is restricted (parental control?)";
+
+/* Contact Image, settings, message when access to contacts is in unknown state */
+"Access to contacts - unknown state" = "Access to contacts - unknown state";
+
+/* Contact Image, settings, short description of the feature */
+"A Contact Image can be used to get live updates from Trio to your Apple Watch Contact complication and/or your iPhone Contact widget." = "A Contact Image can be used to get live updates from Trio to your Apple Watch Contact complication and/or your iPhone Contact widget.";
+
+/* Contact Image, settings, main header */
+"Contact Image" = "Contact Image";
+
+/* Contact Image, settings, label for the layout picker */
+"Layout" = "Layout";
+
+/* Contact Image, settings, single layout name */
+"Single" = "Single";
+
+/* Contact Image, settings, split layout name */
+"Split" = "Split";
+
+/* Contact Image, settings, outer ring - don't show */
+"DontShowRing" = "Don't show";
+
+/* Contact Image, settings, outer ring - Loop status */
+"LoopStatusRing" = "Loop status";
+
+/* Contact Image, settings, outer ring - IOB */
+"IOBRing" = "IOB";
+
+/* Contact Image, settings, outer ring - COB */
+"COBRing" = "COB";
+
+/* Contact Image, settings, outer ring - IOB+COB */
+"IOB+COBRing" = "IOB+COB";
+
+/* Contact Image, settings, label for the primary value picker */
+"Primary" = "Primary";
+
+/* Contact Image, settings, label for the top value picker */
+"Top" = "Top";
+
+/* Contact Image, settings, label for the bottom value picker */
+"Bottom" = "Bottom";
+
+/* Contact Image, settings, header for the outer ring section */
+"Ring" = "Ring";
+
+/* Contact Image, settings, label for the outer ring picker */
+"Outer" = "Outer";
+
+/* Contact Image, settings, label for the ring width picker */
+"Width" = "Width";
+
+/* Contact Image, settings, label for the ring gap picker */
+"Gap" = "Gap";
+
+/* Contact Image, settings, header for the font section */
+"Font" = "Font";
+
+/* Contact Image, settings, label for the font size picker */
+"Size" = "Size";
+
+/* Contact Image, settings, label for the secondary font size picker */
+"Secondary size" = "Secondary size";
+
+/* Contact Image, settings, label for the font tracking picker (tracking - horizontal spacing between letters) */
+"Tracking" = "Tracking";
+
+/* Contact Image, settings, label for the font weight picker */
+"Weight" = "Weight";
+
+/* Contact Image, settings, label for the dark-mode switcher */
+"Dark mode" = "Dark mode";
+
+/* Contact Image, settings, tighter font tracking */
+"TighterFontTracking" = "Tighter";
+
+/* Contact Image, settings, tight font tracking */
+"TightFontTracking" = "Tight";
+
+/* Contact Image, settings, normal font tracking */
+"NormalFontTracking" = "Normal";
+
+/* Contact Image, settings, wide font tracking */
+"WideFontTracking" = "Wide";
+
+/* Contact Image, settings, light font weight */
+"LightFontWeight" = "Light";
+
+/* Contact Image, settings, regular font weight */
+"RegularFontWeight" = "Regular";
+
+/* Contact Image, settings, medium font weight */
+"MediumFontWeight" = "Medium";
+
+/* Contact Image, settings, semibold font weight */
+"SemiboldFontWeight" = "Semibold";
+
+/* Contact Image, settings, bold font weight */
+"BoldFontWeight" = "Bold";
+
+/* Contact Image, settings, black font weight (black is "very bold") */
+"BlackFontWeight" = "Black";
+
+/* Contact Image, settings, don't display any value */
+"NoneContactValue" = "None";
+
+/* Contact Image, settings, Glucose (data to display) */
+"GlucoseContactValue" = "Glucose";
+
+/* Contact Image, settings, Eventual BG (data to display) */
+"EventualBGContactValue" = "Eventual BG";
+
+/* Contact Image, settings, Delta (data to display) */
+"DeltaContactValue" = "Delta";
+
+/* Contact Image, settings, Trend (data to display) */
+"TrendContactValue" = "Trend";
+
+/* Contact Image, settings, Last loop time (data to display) */
+"LastLoopTimeContactValue" = "Last loop time";
+
+/* Contact Image, settings, COB (data to display) */
+"COBContactValue" = "COB";
+
+/* Contact Image, settings, IOB (data to display) */
+"IOBContactValue" = "IOB";
+
+/* Contact Image, settings, loop status (data to display) */
+"LoopStatusContactValue" = "Loop status";
+
+/* ---------------------------------------------------------------------------------------------------------------
   Infotexts from openaps.docs and androidaps.docs
   Trio
 */

+ 152 - 1
FreeAPS/Sources/Localizations/Main/fi.lproj/Localizable.strings

@@ -1858,8 +1858,159 @@ Enact a temp Basal or a temp target */
 /* Smoothing of CGM readings */
 "Smooth Glucose Value" = "Smooth Glucose Value";
 
-/* -----------------------------------------------------------------------------------------------------------
+/* --------------------------------------------------------  Contact Image --------------------------------------*/
 
+/* Contact Image, contacts list - prefix before the contact given name */
+"Contact" = "Contact";
+
+/* Contact Image, settings, header of the contacts list */
+"Contacts" = "Contacts";
+
+/* Contact Image, settings, save your changes reminedr */
+"Don't forget to save your changes." = "Don't forget to save your changes.";
+
+/* Contact Image, settings, message about need to access contacts */
+"Trio needs access to your contacts for this feature to work" = "Trio needs access to your contacts for this feature to work";
+
+/* Contact Image, settings, button to grant access to contacts */
+"Grant Trio access to contacts" = "Grant Trio access to contacts";
+
+/* Contact Image, settings, message when access to contacts denied */
+"Access to contacts denied" = "Access to contacts denied";
+
+/* Contact Image, settings, message when access to contacts restricted */
+"Access to contacts is restricted (parental control?)" = "Access to contacts is restricted (parental control?)";
+
+/* Contact Image, settings, message when access to contacts is in unknown state */
+"Access to contacts - unknown state" = "Access to contacts - unknown state";
+
+/* Contact Image, settings, short description of the feature */
+"A Contact Image can be used to get live updates from Trio to your Apple Watch Contact complication and/or your iPhone Contact widget." = "A Contact Image can be used to get live updates from Trio to your Apple Watch Contact complication and/or your iPhone Contact widget.";
+
+/* Contact Image, settings, main header */
+"Contact Image" = "Contact Image";
+
+/* Contact Image, settings, label for the layout picker */
+"Layout" = "Layout";
+
+/* Contact Image, settings, single layout name */
+"Single" = "Single";
+
+/* Contact Image, settings, split layout name */
+"Split" = "Split";
+
+/* Contact Image, settings, outer ring - don't show */
+"DontShowRing" = "Don't show";
+
+/* Contact Image, settings, outer ring - Loop status */
+"LoopStatusRing" = "Loop status";
+
+/* Contact Image, settings, outer ring - IOB */
+"IOBRing" = "IOB";
+
+/* Contact Image, settings, outer ring - COB */
+"COBRing" = "COB";
+
+/* Contact Image, settings, outer ring - IOB+COB */
+"IOB+COBRing" = "IOB+COB";
+
+/* Contact Image, settings, label for the primary value picker */
+"Primary" = "Primary";
+
+/* Contact Image, settings, label for the top value picker */
+"Top" = "Top";
+
+/* Contact Image, settings, label for the bottom value picker */
+"Bottom" = "Bottom";
+
+/* Contact Image, settings, header for the outer ring section */
+"Ring" = "Ring";
+
+/* Contact Image, settings, label for the outer ring picker */
+"Outer" = "Outer";
+
+/* Contact Image, settings, label for the ring width picker */
+"Width" = "Width";
+
+/* Contact Image, settings, label for the ring gap picker */
+"Gap" = "Gap";
+
+/* Contact Image, settings, header for the font section */
+"Font" = "Font";
+
+/* Contact Image, settings, label for the font size picker */
+"Size" = "Size";
+
+/* Contact Image, settings, label for the secondary font size picker */
+"Secondary size" = "Secondary size";
+
+/* Contact Image, settings, label for the font tracking picker (tracking - horizontal spacing between letters) */
+"Tracking" = "Tracking";
+
+/* Contact Image, settings, label for the font weight picker */
+"Weight" = "Weight";
+
+/* Contact Image, settings, label for the dark-mode switcher */
+"Dark mode" = "Dark mode";
+
+/* Contact Image, settings, tighter font tracking */
+"TighterFontTracking" = "Tighter";
+
+/* Contact Image, settings, tight font tracking */
+"TightFontTracking" = "Tight";
+
+/* Contact Image, settings, normal font tracking */
+"NormalFontTracking" = "Normal";
+
+/* Contact Image, settings, wide font tracking */
+"WideFontTracking" = "Wide";
+
+/* Contact Image, settings, light font weight */
+"LightFontWeight" = "Light";
+
+/* Contact Image, settings, regular font weight */
+"RegularFontWeight" = "Regular";
+
+/* Contact Image, settings, medium font weight */
+"MediumFontWeight" = "Medium";
+
+/* Contact Image, settings, semibold font weight */
+"SemiboldFontWeight" = "Semibold";
+
+/* Contact Image, settings, bold font weight */
+"BoldFontWeight" = "Bold";
+
+/* Contact Image, settings, black font weight (black is "very bold") */
+"BlackFontWeight" = "Black";
+
+/* Contact Image, settings, don't display any value */
+"NoneContactValue" = "None";
+
+/* Contact Image, settings, Glucose (data to display) */
+"GlucoseContactValue" = "Glucose";
+
+/* Contact Image, settings, Eventual BG (data to display) */
+"EventualBGContactValue" = "Eventual BG";
+
+/* Contact Image, settings, Delta (data to display) */
+"DeltaContactValue" = "Delta";
+
+/* Contact Image, settings, Trend (data to display) */
+"TrendContactValue" = "Trend";
+
+/* Contact Image, settings, Last loop time (data to display) */
+"LastLoopTimeContactValue" = "Last loop time";
+
+/* Contact Image, settings, COB (data to display) */
+"COBContactValue" = "COB";
+
+/* Contact Image, settings, IOB (data to display) */
+"IOBContactValue" = "IOB";
+
+/* Contact Image, settings, loop status (data to display) */
+"LoopStatusContactValue" = "Loop status";
+
+/* ---------------------------------------------------------------------------------------------------------------
   Infotexts from openaps.docs and androidaps.docs
   Trio
 */

+ 152 - 1
FreeAPS/Sources/Localizations/Main/fr.lproj/Localizable.strings

@@ -1852,8 +1852,159 @@ Enact a temp Basal or a temp target */
 /* Smoothing of CGM readings */
 "Smooth Glucose Value" = "Valeur Glycémie lisse";
 
-/* -----------------------------------------------------------------------------------------------------------
+/* --------------------------------------------------------  Contact Image --------------------------------------*/
 
+/* Contact Image, contacts list - prefix before the contact given name */
+"Contact" = "Contact";
+
+/* Contact Image, settings, header of the contacts list */
+"Contacts" = "Contacts";
+
+/* Contact Image, settings, save your changes reminedr */
+"Don't forget to save your changes." = "Don't forget to save your changes.";
+
+/* Contact Image, settings, message about need to access contacts */
+"Trio needs access to your contacts for this feature to work" = "Trio needs access to your contacts for this feature to work";
+
+/* Contact Image, settings, button to grant access to contacts */
+"Grant Trio access to contacts" = "Grant Trio access to contacts";
+
+/* Contact Image, settings, message when access to contacts denied */
+"Access to contacts denied" = "Access to contacts denied";
+
+/* Contact Image, settings, message when access to contacts restricted */
+"Access to contacts is restricted (parental control?)" = "Access to contacts is restricted (parental control?)";
+
+/* Contact Image, settings, message when access to contacts is in unknown state */
+"Access to contacts - unknown state" = "Access to contacts - unknown state";
+
+/* Contact Image, settings, short description of the feature */
+"A Contact Image can be used to get live updates from Trio to your Apple Watch Contact complication and/or your iPhone Contact widget." = "A Contact Image can be used to get live updates from Trio to your Apple Watch Contact complication and/or your iPhone Contact widget.";
+
+/* Contact Image, settings, main header */
+"Contact Image" = "Contact Image";
+
+/* Contact Image, settings, label for the layout picker */
+"Layout" = "Layout";
+
+/* Contact Image, settings, single layout name */
+"Single" = "Single";
+
+/* Contact Image, settings, split layout name */
+"Split" = "Split";
+
+/* Contact Image, settings, outer ring - don't show */
+"DontShowRing" = "Don't show";
+
+/* Contact Image, settings, outer ring - Loop status */
+"LoopStatusRing" = "Loop status";
+
+/* Contact Image, settings, outer ring - IOB */
+"IOBRing" = "IOB";
+
+/* Contact Image, settings, outer ring - COB */
+"COBRing" = "COB";
+
+/* Contact Image, settings, outer ring - IOB+COB */
+"IOB+COBRing" = "IOB+COB";
+
+/* Contact Image, settings, label for the primary value picker */
+"Primary" = "Primary";
+
+/* Contact Image, settings, label for the top value picker */
+"Top" = "Top";
+
+/* Contact Image, settings, label for the bottom value picker */
+"Bottom" = "Bottom";
+
+/* Contact Image, settings, header for the outer ring section */
+"Ring" = "Ring";
+
+/* Contact Image, settings, label for the outer ring picker */
+"Outer" = "Outer";
+
+/* Contact Image, settings, label for the ring width picker */
+"Width" = "Width";
+
+/* Contact Image, settings, label for the ring gap picker */
+"Gap" = "Gap";
+
+/* Contact Image, settings, header for the font section */
+"Font" = "Font";
+
+/* Contact Image, settings, label for the font size picker */
+"Size" = "Size";
+
+/* Contact Image, settings, label for the secondary font size picker */
+"Secondary size" = "Secondary size";
+
+/* Contact Image, settings, label for the font tracking picker (tracking - horizontal spacing between letters) */
+"Tracking" = "Tracking";
+
+/* Contact Image, settings, label for the font weight picker */
+"Weight" = "Weight";
+
+/* Contact Image, settings, label for the dark-mode switcher */
+"Dark mode" = "Dark mode";
+
+/* Contact Image, settings, tighter font tracking */
+"TighterFontTracking" = "Tighter";
+
+/* Contact Image, settings, tight font tracking */
+"TightFontTracking" = "Tight";
+
+/* Contact Image, settings, normal font tracking */
+"NormalFontTracking" = "Normal";
+
+/* Contact Image, settings, wide font tracking */
+"WideFontTracking" = "Wide";
+
+/* Contact Image, settings, light font weight */
+"LightFontWeight" = "Light";
+
+/* Contact Image, settings, regular font weight */
+"RegularFontWeight" = "Regular";
+
+/* Contact Image, settings, medium font weight */
+"MediumFontWeight" = "Medium";
+
+/* Contact Image, settings, semibold font weight */
+"SemiboldFontWeight" = "Semibold";
+
+/* Contact Image, settings, bold font weight */
+"BoldFontWeight" = "Bold";
+
+/* Contact Image, settings, black font weight (black is "very bold") */
+"BlackFontWeight" = "Black";
+
+/* Contact Image, settings, don't display any value */
+"NoneContactValue" = "None";
+
+/* Contact Image, settings, Glucose (data to display) */
+"GlucoseContactValue" = "Glucose";
+
+/* Contact Image, settings, Eventual BG (data to display) */
+"EventualBGContactValue" = "Eventual BG";
+
+/* Contact Image, settings, Delta (data to display) */
+"DeltaContactValue" = "Delta";
+
+/* Contact Image, settings, Trend (data to display) */
+"TrendContactValue" = "Trend";
+
+/* Contact Image, settings, Last loop time (data to display) */
+"LastLoopTimeContactValue" = "Last loop time";
+
+/* Contact Image, settings, COB (data to display) */
+"COBContactValue" = "COB";
+
+/* Contact Image, settings, IOB (data to display) */
+"IOBContactValue" = "IOB";
+
+/* Contact Image, settings, loop status (data to display) */
+"LoopStatusContactValue" = "Loop status";
+
+/* ---------------------------------------------------------------------------------------------------------------
   Infotexts from openaps.docs and androidaps.docs
   Trio
 */

+ 152 - 1
FreeAPS/Sources/Localizations/Main/he.lproj/Localizable.strings

@@ -1852,8 +1852,159 @@ Enact a temp Basal or a temp target */
 /* Smoothing of CGM readings */
 "Smooth Glucose Value" = "Smooth Glucose Value";
 
-/* -----------------------------------------------------------------------------------------------------------
+/* --------------------------------------------------------  Contact Image --------------------------------------*/
 
+/* Contact Image, contacts list - prefix before the contact given name */
+"Contact" = "Contact";
+
+/* Contact Image, settings, header of the contacts list */
+"Contacts" = "Contacts";
+
+/* Contact Image, settings, save your changes reminedr */
+"Don't forget to save your changes." = "Don't forget to save your changes.";
+
+/* Contact Image, settings, message about need to access contacts */
+"Trio needs access to your contacts for this feature to work" = "Trio needs access to your contacts for this feature to work";
+
+/* Contact Image, settings, button to grant access to contacts */
+"Grant Trio access to contacts" = "Grant Trio access to contacts";
+
+/* Contact Image, settings, message when access to contacts denied */
+"Access to contacts denied" = "Access to contacts denied";
+
+/* Contact Image, settings, message when access to contacts restricted */
+"Access to contacts is restricted (parental control?)" = "Access to contacts is restricted (parental control?)";
+
+/* Contact Image, settings, message when access to contacts is in unknown state */
+"Access to contacts - unknown state" = "Access to contacts - unknown state";
+
+/* Contact Image, settings, short description of the feature */
+"A Contact Image can be used to get live updates from Trio to your Apple Watch Contact complication and/or your iPhone Contact widget." = "A Contact Image can be used to get live updates from Trio to your Apple Watch Contact complication and/or your iPhone Contact widget.";
+
+/* Contact Image, settings, main header */
+"Contact Image" = "Contact Image";
+
+/* Contact Image, settings, label for the layout picker */
+"Layout" = "Layout";
+
+/* Contact Image, settings, single layout name */
+"Single" = "Single";
+
+/* Contact Image, settings, split layout name */
+"Split" = "Split";
+
+/* Contact Image, settings, outer ring - don't show */
+"DontShowRing" = "Don't show";
+
+/* Contact Image, settings, outer ring - Loop status */
+"LoopStatusRing" = "Loop status";
+
+/* Contact Image, settings, outer ring - IOB */
+"IOBRing" = "IOB";
+
+/* Contact Image, settings, outer ring - COB */
+"COBRing" = "COB";
+
+/* Contact Image, settings, outer ring - IOB+COB */
+"IOB+COBRing" = "IOB+COB";
+
+/* Contact Image, settings, label for the primary value picker */
+"Primary" = "Primary";
+
+/* Contact Image, settings, label for the top value picker */
+"Top" = "Top";
+
+/* Contact Image, settings, label for the bottom value picker */
+"Bottom" = "Bottom";
+
+/* Contact Image, settings, header for the outer ring section */
+"Ring" = "Ring";
+
+/* Contact Image, settings, label for the outer ring picker */
+"Outer" = "Outer";
+
+/* Contact Image, settings, label for the ring width picker */
+"Width" = "Width";
+
+/* Contact Image, settings, label for the ring gap picker */
+"Gap" = "Gap";
+
+/* Contact Image, settings, header for the font section */
+"Font" = "Font";
+
+/* Contact Image, settings, label for the font size picker */
+"Size" = "Size";
+
+/* Contact Image, settings, label for the secondary font size picker */
+"Secondary size" = "Secondary size";
+
+/* Contact Image, settings, label for the font tracking picker (tracking - horizontal spacing between letters) */
+"Tracking" = "Tracking";
+
+/* Contact Image, settings, label for the font weight picker */
+"Weight" = "Weight";
+
+/* Contact Image, settings, label for the dark-mode switcher */
+"Dark mode" = "Dark mode";
+
+/* Contact Image, settings, tighter font tracking */
+"TighterFontTracking" = "Tighter";
+
+/* Contact Image, settings, tight font tracking */
+"TightFontTracking" = "Tight";
+
+/* Contact Image, settings, normal font tracking */
+"NormalFontTracking" = "Normal";
+
+/* Contact Image, settings, wide font tracking */
+"WideFontTracking" = "Wide";
+
+/* Contact Image, settings, light font weight */
+"LightFontWeight" = "Light";
+
+/* Contact Image, settings, regular font weight */
+"RegularFontWeight" = "Regular";
+
+/* Contact Image, settings, medium font weight */
+"MediumFontWeight" = "Medium";
+
+/* Contact Image, settings, semibold font weight */
+"SemiboldFontWeight" = "Semibold";
+
+/* Contact Image, settings, bold font weight */
+"BoldFontWeight" = "Bold";
+
+/* Contact Image, settings, black font weight (black is "very bold") */
+"BlackFontWeight" = "Black";
+
+/* Contact Image, settings, don't display any value */
+"NoneContactValue" = "None";
+
+/* Contact Image, settings, Glucose (data to display) */
+"GlucoseContactValue" = "Glucose";
+
+/* Contact Image, settings, Eventual BG (data to display) */
+"EventualBGContactValue" = "Eventual BG";
+
+/* Contact Image, settings, Delta (data to display) */
+"DeltaContactValue" = "Delta";
+
+/* Contact Image, settings, Trend (data to display) */
+"TrendContactValue" = "Trend";
+
+/* Contact Image, settings, Last loop time (data to display) */
+"LastLoopTimeContactValue" = "Last loop time";
+
+/* Contact Image, settings, COB (data to display) */
+"COBContactValue" = "COB";
+
+/* Contact Image, settings, IOB (data to display) */
+"IOBContactValue" = "IOB";
+
+/* Contact Image, settings, loop status (data to display) */
+"LoopStatusContactValue" = "Loop status";
+
+/* ---------------------------------------------------------------------------------------------------------------
   Infotexts from openaps.docs and androidaps.docs
   Trio
 */

+ 152 - 1
FreeAPS/Sources/Localizations/Main/hu.lproj/Localizable.strings

@@ -1810,8 +1810,159 @@ Enact a temp Basal or a temp target */
 /* Smoothing of CGM readings */
 "Smooth Glucose Value" = "Smooth Glucose Value";
 
-/* -----------------------------------------------------------------------------------------------------------
+/* --------------------------------------------------------  Contact Image --------------------------------------*/
 
+/* Contact Image, contacts list - prefix before the contact given name */
+"Contact" = "Contact";
+
+/* Contact Image, settings, header of the contacts list */
+"Contacts" = "Contacts";
+
+/* Contact Image, settings, save your changes reminedr */
+"Don't forget to save your changes." = "Don't forget to save your changes.";
+
+/* Contact Image, settings, message about need to access contacts */
+"Trio needs access to your contacts for this feature to work" = "Trio needs access to your contacts for this feature to work";
+
+/* Contact Image, settings, button to grant access to contacts */
+"Grant Trio access to contacts" = "Grant Trio access to contacts";
+
+/* Contact Image, settings, message when access to contacts denied */
+"Access to contacts denied" = "Access to contacts denied";
+
+/* Contact Image, settings, message when access to contacts restricted */
+"Access to contacts is restricted (parental control?)" = "Access to contacts is restricted (parental control?)";
+
+/* Contact Image, settings, message when access to contacts is in unknown state */
+"Access to contacts - unknown state" = "Access to contacts - unknown state";
+
+/* Contact Image, settings, short description of the feature */
+"A Contact Image can be used to get live updates from Trio to your Apple Watch Contact complication and/or your iPhone Contact widget." = "A Contact Image can be used to get live updates from Trio to your Apple Watch Contact complication and/or your iPhone Contact widget.";
+
+/* Contact Image, settings, main header */
+"Contact Image" = "Contact Image";
+
+/* Contact Image, settings, label for the layout picker */
+"Layout" = "Layout";
+
+/* Contact Image, settings, single layout name */
+"Single" = "Single";
+
+/* Contact Image, settings, split layout name */
+"Split" = "Split";
+
+/* Contact Image, settings, outer ring - don't show */
+"DontShowRing" = "Don't show";
+
+/* Contact Image, settings, outer ring - Loop status */
+"LoopStatusRing" = "Loop status";
+
+/* Contact Image, settings, outer ring - IOB */
+"IOBRing" = "IOB";
+
+/* Contact Image, settings, outer ring - COB */
+"COBRing" = "COB";
+
+/* Contact Image, settings, outer ring - IOB+COB */
+"IOB+COBRing" = "IOB+COB";
+
+/* Contact Image, settings, label for the primary value picker */
+"Primary" = "Primary";
+
+/* Contact Image, settings, label for the top value picker */
+"Top" = "Top";
+
+/* Contact Image, settings, label for the bottom value picker */
+"Bottom" = "Bottom";
+
+/* Contact Image, settings, header for the outer ring section */
+"Ring" = "Ring";
+
+/* Contact Image, settings, label for the outer ring picker */
+"Outer" = "Outer";
+
+/* Contact Image, settings, label for the ring width picker */
+"Width" = "Width";
+
+/* Contact Image, settings, label for the ring gap picker */
+"Gap" = "Gap";
+
+/* Contact Image, settings, header for the font section */
+"Font" = "Font";
+
+/* Contact Image, settings, label for the font size picker */
+"Size" = "Size";
+
+/* Contact Image, settings, label for the secondary font size picker */
+"Secondary size" = "Secondary size";
+
+/* Contact Image, settings, label for the font tracking picker (tracking - horizontal spacing between letters) */
+"Tracking" = "Tracking";
+
+/* Contact Image, settings, label for the font weight picker */
+"Weight" = "Weight";
+
+/* Contact Image, settings, label for the dark-mode switcher */
+"Dark mode" = "Dark mode";
+
+/* Contact Image, settings, tighter font tracking */
+"TighterFontTracking" = "Tighter";
+
+/* Contact Image, settings, tight font tracking */
+"TightFontTracking" = "Tight";
+
+/* Contact Image, settings, normal font tracking */
+"NormalFontTracking" = "Normal";
+
+/* Contact Image, settings, wide font tracking */
+"WideFontTracking" = "Wide";
+
+/* Contact Image, settings, light font weight */
+"LightFontWeight" = "Light";
+
+/* Contact Image, settings, regular font weight */
+"RegularFontWeight" = "Regular";
+
+/* Contact Image, settings, medium font weight */
+"MediumFontWeight" = "Medium";
+
+/* Contact Image, settings, semibold font weight */
+"SemiboldFontWeight" = "Semibold";
+
+/* Contact Image, settings, bold font weight */
+"BoldFontWeight" = "Bold";
+
+/* Contact Image, settings, black font weight (black is "very bold") */
+"BlackFontWeight" = "Black";
+
+/* Contact Image, settings, don't display any value */
+"NoneContactValue" = "None";
+
+/* Contact Image, settings, Glucose (data to display) */
+"GlucoseContactValue" = "Glucose";
+
+/* Contact Image, settings, Eventual BG (data to display) */
+"EventualBGContactValue" = "Eventual BG";
+
+/* Contact Image, settings, Delta (data to display) */
+"DeltaContactValue" = "Delta";
+
+/* Contact Image, settings, Trend (data to display) */
+"TrendContactValue" = "Trend";
+
+/* Contact Image, settings, Last loop time (data to display) */
+"LastLoopTimeContactValue" = "Last loop time";
+
+/* Contact Image, settings, COB (data to display) */
+"COBContactValue" = "COB";
+
+/* Contact Image, settings, IOB (data to display) */
+"IOBContactValue" = "IOB";
+
+/* Contact Image, settings, loop status (data to display) */
+"LoopStatusContactValue" = "Loop status";
+
+/* ---------------------------------------------------------------------------------------------------------------
   Infotexts from openaps.docs and androidaps.docs
   Trio
 */

+ 152 - 1
FreeAPS/Sources/Localizations/Main/it.lproj/Localizable.strings

@@ -1852,8 +1852,159 @@ Enact a temp Basal or a temp target */
 /* Smoothing of CGM readings */
 "Smooth Glucose Value" = "Valori Glicemici Levigati";
 
-/* -----------------------------------------------------------------------------------------------------------
+/* --------------------------------------------------------  Contact Image --------------------------------------*/
 
+/* Contact Image, contacts list - prefix before the contact given name */
+"Contact" = "Contact";
+
+/* Contact Image, settings, header of the contacts list */
+"Contacts" = "Contacts";
+
+/* Contact Image, settings, save your changes reminedr */
+"Don't forget to save your changes." = "Don't forget to save your changes.";
+
+/* Contact Image, settings, message about need to access contacts */
+"Trio needs access to your contacts for this feature to work" = "Trio needs access to your contacts for this feature to work";
+
+/* Contact Image, settings, button to grant access to contacts */
+"Grant Trio access to contacts" = "Grant Trio access to contacts";
+
+/* Contact Image, settings, message when access to contacts denied */
+"Access to contacts denied" = "Access to contacts denied";
+
+/* Contact Image, settings, message when access to contacts restricted */
+"Access to contacts is restricted (parental control?)" = "Access to contacts is restricted (parental control?)";
+
+/* Contact Image, settings, message when access to contacts is in unknown state */
+"Access to contacts - unknown state" = "Access to contacts - unknown state";
+
+/* Contact Image, settings, short description of the feature */
+"A Contact Image can be used to get live updates from Trio to your Apple Watch Contact complication and/or your iPhone Contact widget." = "A Contact Image can be used to get live updates from Trio to your Apple Watch Contact complication and/or your iPhone Contact widget.";
+
+/* Contact Image, settings, main header */
+"Contact Image" = "Contact Image";
+
+/* Contact Image, settings, label for the layout picker */
+"Layout" = "Layout";
+
+/* Contact Image, settings, single layout name */
+"Single" = "Single";
+
+/* Contact Image, settings, split layout name */
+"Split" = "Split";
+
+/* Contact Image, settings, outer ring - don't show */
+"DontShowRing" = "Don't show";
+
+/* Contact Image, settings, outer ring - Loop status */
+"LoopStatusRing" = "Loop status";
+
+/* Contact Image, settings, outer ring - IOB */
+"IOBRing" = "IOB";
+
+/* Contact Image, settings, outer ring - COB */
+"COBRing" = "COB";
+
+/* Contact Image, settings, outer ring - IOB+COB */
+"IOB+COBRing" = "IOB+COB";
+
+/* Contact Image, settings, label for the primary value picker */
+"Primary" = "Primary";
+
+/* Contact Image, settings, label for the top value picker */
+"Top" = "Top";
+
+/* Contact Image, settings, label for the bottom value picker */
+"Bottom" = "Bottom";
+
+/* Contact Image, settings, header for the outer ring section */
+"Ring" = "Ring";
+
+/* Contact Image, settings, label for the outer ring picker */
+"Outer" = "Outer";
+
+/* Contact Image, settings, label for the ring width picker */
+"Width" = "Width";
+
+/* Contact Image, settings, label for the ring gap picker */
+"Gap" = "Gap";
+
+/* Contact Image, settings, header for the font section */
+"Font" = "Font";
+
+/* Contact Image, settings, label for the font size picker */
+"Size" = "Size";
+
+/* Contact Image, settings, label for the secondary font size picker */
+"Secondary size" = "Secondary size";
+
+/* Contact Image, settings, label for the font tracking picker (tracking - horizontal spacing between letters) */
+"Tracking" = "Tracking";
+
+/* Contact Image, settings, label for the font weight picker */
+"Weight" = "Weight";
+
+/* Contact Image, settings, label for the dark-mode switcher */
+"Dark mode" = "Dark mode";
+
+/* Contact Image, settings, tighter font tracking */
+"TighterFontTracking" = "Tighter";
+
+/* Contact Image, settings, tight font tracking */
+"TightFontTracking" = "Tight";
+
+/* Contact Image, settings, normal font tracking */
+"NormalFontTracking" = "Normal";
+
+/* Contact Image, settings, wide font tracking */
+"WideFontTracking" = "Wide";
+
+/* Contact Image, settings, light font weight */
+"LightFontWeight" = "Light";
+
+/* Contact Image, settings, regular font weight */
+"RegularFontWeight" = "Regular";
+
+/* Contact Image, settings, medium font weight */
+"MediumFontWeight" = "Medium";
+
+/* Contact Image, settings, semibold font weight */
+"SemiboldFontWeight" = "Semibold";
+
+/* Contact Image, settings, bold font weight */
+"BoldFontWeight" = "Bold";
+
+/* Contact Image, settings, black font weight (black is "very bold") */
+"BlackFontWeight" = "Black";
+
+/* Contact Image, settings, don't display any value */
+"NoneContactValue" = "None";
+
+/* Contact Image, settings, Glucose (data to display) */
+"GlucoseContactValue" = "Glucose";
+
+/* Contact Image, settings, Eventual BG (data to display) */
+"EventualBGContactValue" = "Eventual BG";
+
+/* Contact Image, settings, Delta (data to display) */
+"DeltaContactValue" = "Delta";
+
+/* Contact Image, settings, Trend (data to display) */
+"TrendContactValue" = "Trend";
+
+/* Contact Image, settings, Last loop time (data to display) */
+"LastLoopTimeContactValue" = "Last loop time";
+
+/* Contact Image, settings, COB (data to display) */
+"COBContactValue" = "COB";
+
+/* Contact Image, settings, IOB (data to display) */
+"IOBContactValue" = "IOB";
+
+/* Contact Image, settings, loop status (data to display) */
+"LoopStatusContactValue" = "Loop status";
+
+/* ---------------------------------------------------------------------------------------------------------------
   Infotexts from openaps.docs and androidaps.docs
   Trio
 */

+ 152 - 1
FreeAPS/Sources/Localizations/Main/nb.lproj/Localizable.strings

@@ -1852,8 +1852,159 @@ Enact a temp Basal or a temp target */
 /* Smoothing of CGM readings */
 "Smooth Glucose Value" = "Utjevning av blodsukkerverdi";
 
-/* -----------------------------------------------------------------------------------------------------------
+/* --------------------------------------------------------  Contact Image --------------------------------------*/
 
+/* Contact Image, contacts list - prefix before the contact given name */
+"Contact" = "Contact";
+
+/* Contact Image, settings, header of the contacts list */
+"Contacts" = "Contacts";
+
+/* Contact Image, settings, save your changes reminedr */
+"Don't forget to save your changes." = "Don't forget to save your changes.";
+
+/* Contact Image, settings, message about need to access contacts */
+"Trio needs access to your contacts for this feature to work" = "Trio needs access to your contacts for this feature to work";
+
+/* Contact Image, settings, button to grant access to contacts */
+"Grant Trio access to contacts" = "Grant Trio access to contacts";
+
+/* Contact Image, settings, message when access to contacts denied */
+"Access to contacts denied" = "Access to contacts denied";
+
+/* Contact Image, settings, message when access to contacts restricted */
+"Access to contacts is restricted (parental control?)" = "Access to contacts is restricted (parental control?)";
+
+/* Contact Image, settings, message when access to contacts is in unknown state */
+"Access to contacts - unknown state" = "Access to contacts - unknown state";
+
+/* Contact Image, settings, short description of the feature */
+"A Contact Image can be used to get live updates from Trio to your Apple Watch Contact complication and/or your iPhone Contact widget." = "A Contact Image can be used to get live updates from Trio to your Apple Watch Contact complication and/or your iPhone Contact widget.";
+
+/* Contact Image, settings, main header */
+"Contact Image" = "Contact Image";
+
+/* Contact Image, settings, label for the layout picker */
+"Layout" = "Layout";
+
+/* Contact Image, settings, single layout name */
+"Single" = "Single";
+
+/* Contact Image, settings, split layout name */
+"Split" = "Split";
+
+/* Contact Image, settings, outer ring - don't show */
+"DontShowRing" = "Don't show";
+
+/* Contact Image, settings, outer ring - Loop status */
+"LoopStatusRing" = "Loop status";
+
+/* Contact Image, settings, outer ring - IOB */
+"IOBRing" = "IOB";
+
+/* Contact Image, settings, outer ring - COB */
+"COBRing" = "COB";
+
+/* Contact Image, settings, outer ring - IOB+COB */
+"IOB+COBRing" = "IOB+COB";
+
+/* Contact Image, settings, label for the primary value picker */
+"Primary" = "Primary";
+
+/* Contact Image, settings, label for the top value picker */
+"Top" = "Top";
+
+/* Contact Image, settings, label for the bottom value picker */
+"Bottom" = "Bottom";
+
+/* Contact Image, settings, header for the outer ring section */
+"Ring" = "Ring";
+
+/* Contact Image, settings, label for the outer ring picker */
+"Outer" = "Outer";
+
+/* Contact Image, settings, label for the ring width picker */
+"Width" = "Width";
+
+/* Contact Image, settings, label for the ring gap picker */
+"Gap" = "Gap";
+
+/* Contact Image, settings, header for the font section */
+"Font" = "Font";
+
+/* Contact Image, settings, label for the font size picker */
+"Size" = "Size";
+
+/* Contact Image, settings, label for the secondary font size picker */
+"Secondary size" = "Secondary size";
+
+/* Contact Image, settings, label for the font tracking picker (tracking - horizontal spacing between letters) */
+"Tracking" = "Tracking";
+
+/* Contact Image, settings, label for the font weight picker */
+"Weight" = "Weight";
+
+/* Contact Image, settings, label for the dark-mode switcher */
+"Dark mode" = "Dark mode";
+
+/* Contact Image, settings, tighter font tracking */
+"TighterFontTracking" = "Tighter";
+
+/* Contact Image, settings, tight font tracking */
+"TightFontTracking" = "Tight";
+
+/* Contact Image, settings, normal font tracking */
+"NormalFontTracking" = "Normal";
+
+/* Contact Image, settings, wide font tracking */
+"WideFontTracking" = "Wide";
+
+/* Contact Image, settings, light font weight */
+"LightFontWeight" = "Light";
+
+/* Contact Image, settings, regular font weight */
+"RegularFontWeight" = "Regular";
+
+/* Contact Image, settings, medium font weight */
+"MediumFontWeight" = "Medium";
+
+/* Contact Image, settings, semibold font weight */
+"SemiboldFontWeight" = "Semibold";
+
+/* Contact Image, settings, bold font weight */
+"BoldFontWeight" = "Bold";
+
+/* Contact Image, settings, black font weight (black is "very bold") */
+"BlackFontWeight" = "Black";
+
+/* Contact Image, settings, don't display any value */
+"NoneContactValue" = "None";
+
+/* Contact Image, settings, Glucose (data to display) */
+"GlucoseContactValue" = "Glucose";
+
+/* Contact Image, settings, Eventual BG (data to display) */
+"EventualBGContactValue" = "Eventual BG";
+
+/* Contact Image, settings, Delta (data to display) */
+"DeltaContactValue" = "Delta";
+
+/* Contact Image, settings, Trend (data to display) */
+"TrendContactValue" = "Trend";
+
+/* Contact Image, settings, Last loop time (data to display) */
+"LastLoopTimeContactValue" = "Last loop time";
+
+/* Contact Image, settings, COB (data to display) */
+"COBContactValue" = "COB";
+
+/* Contact Image, settings, IOB (data to display) */
+"IOBContactValue" = "IOB";
+
+/* Contact Image, settings, loop status (data to display) */
+"LoopStatusContactValue" = "Loop status";
+
+/* ---------------------------------------------------------------------------------------------------------------
   Infotexts from openaps.docs and androidaps.docs
   Trio
 */

+ 152 - 1
FreeAPS/Sources/Localizations/Main/nl.lproj/Localizable.strings

@@ -1835,8 +1835,159 @@ Enact a temp Basal or a temp target */
 /* HbA1c for all glucose storage days */
 "all" = "alle ";
 
-/* --------------------------------------------------------  Dexcom G7 --------------------------------------*/
+/* --------------------------------------------------------  Contact Image --------------------------------------*/
 
+/* Contact Image, contacts list - prefix before the contact given name */
+"Contact" = "Contact";
+
+/* Contact Image, settings, header of the contacts list */
+"Contacts" = "Contacts";
+
+/* Contact Image, settings, save your changes reminedr */
+"Don't forget to save your changes." = "Don't forget to save your changes.";
+
+/* Contact Image, settings, message about need to access contacts */
+"Trio needs access to your contacts for this feature to work" = "Trio needs access to your contacts for this feature to work";
+
+/* Contact Image, settings, button to grant access to contacts */
+"Grant Trio access to contacts" = "Grant Trio access to contacts";
+
+/* Contact Image, settings, message when access to contacts denied */
+"Access to contacts denied" = "Access to contacts denied";
+
+/* Contact Image, settings, message when access to contacts restricted */
+"Access to contacts is restricted (parental control?)" = "Access to contacts is restricted (parental control?)";
+
+/* Contact Image, settings, message when access to contacts is in unknown state */
+"Access to contacts - unknown state" = "Access to contacts - unknown state";
+
+/* Contact Image, settings, short description of the feature */
+"A Contact Image can be used to get live updates from Trio to your Apple Watch Contact complication and/or your iPhone Contact widget." = "A Contact Image can be used to get live updates from Trio to your Apple Watch Contact complication and/or your iPhone Contact widget.";
+
+/* Contact Image, settings, main header */
+"Contact Image" = "Contact Image";
+
+/* Contact Image, settings, label for the layout picker */
+"Layout" = "Layout";
+
+/* Contact Image, settings, single layout name */
+"Single" = "Single";
+
+/* Contact Image, settings, split layout name */
+"Split" = "Split";
+
+/* Contact Image, settings, outer ring - don't show */
+"DontShowRing" = "Don't show";
+
+/* Contact Image, settings, outer ring - Loop status */
+"LoopStatusRing" = "Loop status";
+
+/* Contact Image, settings, outer ring - IOB */
+"IOBRing" = "IOB";
+
+/* Contact Image, settings, outer ring - COB */
+"COBRing" = "COB";
+
+/* Contact Image, settings, outer ring - IOB+COB */
+"IOB+COBRing" = "IOB+COB";
+
+/* Contact Image, settings, label for the primary value picker */
+"Primary" = "Primary";
+
+/* Contact Image, settings, label for the top value picker */
+"Top" = "Top";
+
+/* Contact Image, settings, label for the bottom value picker */
+"Bottom" = "Bottom";
+
+/* Contact Image, settings, header for the outer ring section */
+"Ring" = "Ring";
+
+/* Contact Image, settings, label for the outer ring picker */
+"Outer" = "Outer";
+
+/* Contact Image, settings, label for the ring width picker */
+"Width" = "Width";
+
+/* Contact Image, settings, label for the ring gap picker */
+"Gap" = "Gap";
+
+/* Contact Image, settings, header for the font section */
+"Font" = "Font";
+
+/* Contact Image, settings, label for the font size picker */
+"Size" = "Size";
+
+/* Contact Image, settings, label for the secondary font size picker */
+"Secondary size" = "Secondary size";
+
+/* Contact Image, settings, label for the font tracking picker (tracking - horizontal spacing between letters) */
+"Tracking" = "Tracking";
+
+/* Contact Image, settings, label for the font weight picker */
+"Weight" = "Weight";
+
+/* Contact Image, settings, label for the dark-mode switcher */
+"Dark mode" = "Dark mode";
+
+/* Contact Image, settings, tighter font tracking */
+"TighterFontTracking" = "Tighter";
+
+/* Contact Image, settings, tight font tracking */
+"TightFontTracking" = "Tight";
+
+/* Contact Image, settings, normal font tracking */
+"NormalFontTracking" = "Normal";
+
+/* Contact Image, settings, wide font tracking */
+"WideFontTracking" = "Wide";
+
+/* Contact Image, settings, light font weight */
+"LightFontWeight" = "Light";
+
+/* Contact Image, settings, regular font weight */
+"RegularFontWeight" = "Regular";
+
+/* Contact Image, settings, medium font weight */
+"MediumFontWeight" = "Medium";
+
+/* Contact Image, settings, semibold font weight */
+"SemiboldFontWeight" = "Semibold";
+
+/* Contact Image, settings, bold font weight */
+"BoldFontWeight" = "Bold";
+
+/* Contact Image, settings, black font weight (black is "very bold") */
+"BlackFontWeight" = "Black";
+
+/* Contact Image, settings, don't display any value */
+"NoneContactValue" = "None";
+
+/* Contact Image, settings, Glucose (data to display) */
+"GlucoseContactValue" = "Glucose";
+
+/* Contact Image, settings, Eventual BG (data to display) */
+"EventualBGContactValue" = "Eventual BG";
+
+/* Contact Image, settings, Delta (data to display) */
+"DeltaContactValue" = "Delta";
+
+/* Contact Image, settings, Trend (data to display) */
+"TrendContactValue" = "Trend";
+
+/* Contact Image, settings, Last loop time (data to display) */
+"LastLoopTimeContactValue" = "Last loop time";
+
+/* Contact Image, settings, COB (data to display) */
+"COBContactValue" = "COB";
+
+/* Contact Image, settings, IOB (data to display) */
+"IOBContactValue" = "IOB";
+
+/* Contact Image, settings, loop status (data to display) */
+"LoopStatusContactValue" = "Loop status";
+
+/* ---------------------------------------------------------------------------------------------------------------
 "CGM Configuration" = "CGM instellingen";
 
 "Heartbeat" = "Hartslag";

+ 152 - 1
FreeAPS/Sources/Localizations/Main/pl.lproj/Localizable.strings

@@ -1854,8 +1854,159 @@ Połączono z Nightscout!";
 /* Smoothing of CGM readings */
 "Smooth Glucose Value" = "Smooth Glucose Value";
 
-/* -----------------------------------------------------------------------------------------------------------
+/* --------------------------------------------------------  Contact Image --------------------------------------*/
 
+/* Contact Image, contacts list - prefix before the contact given name */
+"Contact" = "Contact";
+
+/* Contact Image, settings, header of the contacts list */
+"Contacts" = "Contacts";
+
+/* Contact Image, settings, save your changes reminedr */
+"Don't forget to save your changes." = "Don't forget to save your changes.";
+
+/* Contact Image, settings, message about need to access contacts */
+"Trio needs access to your contacts for this feature to work" = "Trio needs access to your contacts for this feature to work";
+
+/* Contact Image, settings, button to grant access to contacts */
+"Grant Trio access to contacts" = "Grant Trio access to contacts";
+
+/* Contact Image, settings, message when access to contacts denied */
+"Access to contacts denied" = "Access to contacts denied";
+
+/* Contact Image, settings, message when access to contacts restricted */
+"Access to contacts is restricted (parental control?)" = "Access to contacts is restricted (parental control?)";
+
+/* Contact Image, settings, message when access to contacts is in unknown state */
+"Access to contacts - unknown state" = "Access to contacts - unknown state";
+
+/* Contact Image, settings, short description of the feature */
+"A Contact Image can be used to get live updates from Trio to your Apple Watch Contact complication and/or your iPhone Contact widget." = "A Contact Image can be used to get live updates from Trio to your Apple Watch Contact complication and/or your iPhone Contact widget.";
+
+/* Contact Image, settings, main header */
+"Contact Image" = "Contact Image";
+
+/* Contact Image, settings, label for the layout picker */
+"Layout" = "Layout";
+
+/* Contact Image, settings, single layout name */
+"Single" = "Single";
+
+/* Contact Image, settings, split layout name */
+"Split" = "Split";
+
+/* Contact Image, settings, outer ring - don't show */
+"DontShowRing" = "Don't show";
+
+/* Contact Image, settings, outer ring - Loop status */
+"LoopStatusRing" = "Loop status";
+
+/* Contact Image, settings, outer ring - IOB */
+"IOBRing" = "IOB";
+
+/* Contact Image, settings, outer ring - COB */
+"COBRing" = "COB";
+
+/* Contact Image, settings, outer ring - IOB+COB */
+"IOB+COBRing" = "IOB+COB";
+
+/* Contact Image, settings, label for the primary value picker */
+"Primary" = "Primary";
+
+/* Contact Image, settings, label for the top value picker */
+"Top" = "Top";
+
+/* Contact Image, settings, label for the bottom value picker */
+"Bottom" = "Bottom";
+
+/* Contact Image, settings, header for the outer ring section */
+"Ring" = "Ring";
+
+/* Contact Image, settings, label for the outer ring picker */
+"Outer" = "Outer";
+
+/* Contact Image, settings, label for the ring width picker */
+"Width" = "Width";
+
+/* Contact Image, settings, label for the ring gap picker */
+"Gap" = "Gap";
+
+/* Contact Image, settings, header for the font section */
+"Font" = "Font";
+
+/* Contact Image, settings, label for the font size picker */
+"Size" = "Size";
+
+/* Contact Image, settings, label for the secondary font size picker */
+"Secondary size" = "Secondary size";
+
+/* Contact Image, settings, label for the font tracking picker (tracking - horizontal spacing between letters) */
+"Tracking" = "Tracking";
+
+/* Contact Image, settings, label for the font weight picker */
+"Weight" = "Weight";
+
+/* Contact Image, settings, label for the dark-mode switcher */
+"Dark mode" = "Dark mode";
+
+/* Contact Image, settings, tighter font tracking */
+"TighterFontTracking" = "Tighter";
+
+/* Contact Image, settings, tight font tracking */
+"TightFontTracking" = "Tight";
+
+/* Contact Image, settings, normal font tracking */
+"NormalFontTracking" = "Normal";
+
+/* Contact Image, settings, wide font tracking */
+"WideFontTracking" = "Wide";
+
+/* Contact Image, settings, light font weight */
+"LightFontWeight" = "Light";
+
+/* Contact Image, settings, regular font weight */
+"RegularFontWeight" = "Regular";
+
+/* Contact Image, settings, medium font weight */
+"MediumFontWeight" = "Medium";
+
+/* Contact Image, settings, semibold font weight */
+"SemiboldFontWeight" = "Semibold";
+
+/* Contact Image, settings, bold font weight */
+"BoldFontWeight" = "Bold";
+
+/* Contact Image, settings, black font weight (black is "very bold") */
+"BlackFontWeight" = "Black";
+
+/* Contact Image, settings, don't display any value */
+"NoneContactValue" = "None";
+
+/* Contact Image, settings, Glucose (data to display) */
+"GlucoseContactValue" = "Glucose";
+
+/* Contact Image, settings, Eventual BG (data to display) */
+"EventualBGContactValue" = "Eventual BG";
+
+/* Contact Image, settings, Delta (data to display) */
+"DeltaContactValue" = "Delta";
+
+/* Contact Image, settings, Trend (data to display) */
+"TrendContactValue" = "Trend";
+
+/* Contact Image, settings, Last loop time (data to display) */
+"LastLoopTimeContactValue" = "Last loop time";
+
+/* Contact Image, settings, COB (data to display) */
+"COBContactValue" = "COB";
+
+/* Contact Image, settings, IOB (data to display) */
+"IOBContactValue" = "IOB";
+
+/* Contact Image, settings, loop status (data to display) */
+"LoopStatusContactValue" = "Loop status";
+
+/* ---------------------------------------------------------------------------------------------------------------
   Infotexts from openaps.docs and androidaps.docs
   Trio
 */

+ 152 - 1
FreeAPS/Sources/Localizations/Main/pt-BR.lproj/Localizable.strings

@@ -1852,8 +1852,159 @@ Enact a temp Basal or a temp target */
 /* Smoothing of CGM readings */
 "Smooth Glucose Value" = "Smooth Glucose Value";
 
-/* -----------------------------------------------------------------------------------------------------------
+/* --------------------------------------------------------  Contact Image --------------------------------------*/
 
+/* Contact Image, contacts list - prefix before the contact given name */
+"Contact" = "Contact";
+
+/* Contact Image, settings, header of the contacts list */
+"Contacts" = "Contacts";
+
+/* Contact Image, settings, save your changes reminedr */
+"Don't forget to save your changes." = "Don't forget to save your changes.";
+
+/* Contact Image, settings, message about need to access contacts */
+"Trio needs access to your contacts for this feature to work" = "Trio needs access to your contacts for this feature to work";
+
+/* Contact Image, settings, button to grant access to contacts */
+"Grant Trio access to contacts" = "Grant Trio access to contacts";
+
+/* Contact Image, settings, message when access to contacts denied */
+"Access to contacts denied" = "Access to contacts denied";
+
+/* Contact Image, settings, message when access to contacts restricted */
+"Access to contacts is restricted (parental control?)" = "Access to contacts is restricted (parental control?)";
+
+/* Contact Image, settings, message when access to contacts is in unknown state */
+"Access to contacts - unknown state" = "Access to contacts - unknown state";
+
+/* Contact Image, settings, short description of the feature */
+"A Contact Image can be used to get live updates from Trio to your Apple Watch Contact complication and/or your iPhone Contact widget." = "A Contact Image can be used to get live updates from Trio to your Apple Watch Contact complication and/or your iPhone Contact widget.";
+
+/* Contact Image, settings, main header */
+"Contact Image" = "Contact Image";
+
+/* Contact Image, settings, label for the layout picker */
+"Layout" = "Layout";
+
+/* Contact Image, settings, single layout name */
+"Single" = "Single";
+
+/* Contact Image, settings, split layout name */
+"Split" = "Split";
+
+/* Contact Image, settings, outer ring - don't show */
+"DontShowRing" = "Don't show";
+
+/* Contact Image, settings, outer ring - Loop status */
+"LoopStatusRing" = "Loop status";
+
+/* Contact Image, settings, outer ring - IOB */
+"IOBRing" = "IOB";
+
+/* Contact Image, settings, outer ring - COB */
+"COBRing" = "COB";
+
+/* Contact Image, settings, outer ring - IOB+COB */
+"IOB+COBRing" = "IOB+COB";
+
+/* Contact Image, settings, label for the primary value picker */
+"Primary" = "Primary";
+
+/* Contact Image, settings, label for the top value picker */
+"Top" = "Top";
+
+/* Contact Image, settings, label for the bottom value picker */
+"Bottom" = "Bottom";
+
+/* Contact Image, settings, header for the outer ring section */
+"Ring" = "Ring";
+
+/* Contact Image, settings, label for the outer ring picker */
+"Outer" = "Outer";
+
+/* Contact Image, settings, label for the ring width picker */
+"Width" = "Width";
+
+/* Contact Image, settings, label for the ring gap picker */
+"Gap" = "Gap";
+
+/* Contact Image, settings, header for the font section */
+"Font" = "Font";
+
+/* Contact Image, settings, label for the font size picker */
+"Size" = "Size";
+
+/* Contact Image, settings, label for the secondary font size picker */
+"Secondary size" = "Secondary size";
+
+/* Contact Image, settings, label for the font tracking picker (tracking - horizontal spacing between letters) */
+"Tracking" = "Tracking";
+
+/* Contact Image, settings, label for the font weight picker */
+"Weight" = "Weight";
+
+/* Contact Image, settings, label for the dark-mode switcher */
+"Dark mode" = "Dark mode";
+
+/* Contact Image, settings, tighter font tracking */
+"TighterFontTracking" = "Tighter";
+
+/* Contact Image, settings, tight font tracking */
+"TightFontTracking" = "Tight";
+
+/* Contact Image, settings, normal font tracking */
+"NormalFontTracking" = "Normal";
+
+/* Contact Image, settings, wide font tracking */
+"WideFontTracking" = "Wide";
+
+/* Contact Image, settings, light font weight */
+"LightFontWeight" = "Light";
+
+/* Contact Image, settings, regular font weight */
+"RegularFontWeight" = "Regular";
+
+/* Contact Image, settings, medium font weight */
+"MediumFontWeight" = "Medium";
+
+/* Contact Image, settings, semibold font weight */
+"SemiboldFontWeight" = "Semibold";
+
+/* Contact Image, settings, bold font weight */
+"BoldFontWeight" = "Bold";
+
+/* Contact Image, settings, black font weight (black is "very bold") */
+"BlackFontWeight" = "Black";
+
+/* Contact Image, settings, don't display any value */
+"NoneContactValue" = "None";
+
+/* Contact Image, settings, Glucose (data to display) */
+"GlucoseContactValue" = "Glucose";
+
+/* Contact Image, settings, Eventual BG (data to display) */
+"EventualBGContactValue" = "Eventual BG";
+
+/* Contact Image, settings, Delta (data to display) */
+"DeltaContactValue" = "Delta";
+
+/* Contact Image, settings, Trend (data to display) */
+"TrendContactValue" = "Trend";
+
+/* Contact Image, settings, Last loop time (data to display) */
+"LastLoopTimeContactValue" = "Last loop time";
+
+/* Contact Image, settings, COB (data to display) */
+"COBContactValue" = "COB";
+
+/* Contact Image, settings, IOB (data to display) */
+"IOBContactValue" = "IOB";
+
+/* Contact Image, settings, loop status (data to display) */
+"LoopStatusContactValue" = "Loop status";
+
+/* ---------------------------------------------------------------------------------------------------------------
   Infotexts from openaps.docs and androidaps.docs
   Trio
 */

+ 152 - 1
FreeAPS/Sources/Localizations/Main/pt-PT.lproj/Localizable.strings

@@ -1852,8 +1852,159 @@ Enact a temp Basal or a temp target */
 /* Smoothing of CGM readings */
 "Smooth Glucose Value" = "Smooth Glucose Value";
 
-/* -----------------------------------------------------------------------------------------------------------
+/* --------------------------------------------------------  Contact Image --------------------------------------*/
 
+/* Contact Image, contacts list - prefix before the contact given name */
+"Contact" = "Contact";
+
+/* Contact Image, settings, header of the contacts list */
+"Contacts" = "Contacts";
+
+/* Contact Image, settings, save your changes reminedr */
+"Don't forget to save your changes." = "Don't forget to save your changes.";
+
+/* Contact Image, settings, message about need to access contacts */
+"Trio needs access to your contacts for this feature to work" = "Trio needs access to your contacts for this feature to work";
+
+/* Contact Image, settings, button to grant access to contacts */
+"Grant Trio access to contacts" = "Grant Trio access to contacts";
+
+/* Contact Image, settings, message when access to contacts denied */
+"Access to contacts denied" = "Access to contacts denied";
+
+/* Contact Image, settings, message when access to contacts restricted */
+"Access to contacts is restricted (parental control?)" = "Access to contacts is restricted (parental control?)";
+
+/* Contact Image, settings, message when access to contacts is in unknown state */
+"Access to contacts - unknown state" = "Access to contacts - unknown state";
+
+/* Contact Image, settings, short description of the feature */
+"A Contact Image can be used to get live updates from Trio to your Apple Watch Contact complication and/or your iPhone Contact widget." = "A Contact Image can be used to get live updates from Trio to your Apple Watch Contact complication and/or your iPhone Contact widget.";
+
+/* Contact Image, settings, main header */
+"Contact Image" = "Contact Image";
+
+/* Contact Image, settings, label for the layout picker */
+"Layout" = "Layout";
+
+/* Contact Image, settings, single layout name */
+"Single" = "Single";
+
+/* Contact Image, settings, split layout name */
+"Split" = "Split";
+
+/* Contact Image, settings, outer ring - don't show */
+"DontShowRing" = "Don't show";
+
+/* Contact Image, settings, outer ring - Loop status */
+"LoopStatusRing" = "Loop status";
+
+/* Contact Image, settings, outer ring - IOB */
+"IOBRing" = "IOB";
+
+/* Contact Image, settings, outer ring - COB */
+"COBRing" = "COB";
+
+/* Contact Image, settings, outer ring - IOB+COB */
+"IOB+COBRing" = "IOB+COB";
+
+/* Contact Image, settings, label for the primary value picker */
+"Primary" = "Primary";
+
+/* Contact Image, settings, label for the top value picker */
+"Top" = "Top";
+
+/* Contact Image, settings, label for the bottom value picker */
+"Bottom" = "Bottom";
+
+/* Contact Image, settings, header for the outer ring section */
+"Ring" = "Ring";
+
+/* Contact Image, settings, label for the outer ring picker */
+"Outer" = "Outer";
+
+/* Contact Image, settings, label for the ring width picker */
+"Width" = "Width";
+
+/* Contact Image, settings, label for the ring gap picker */
+"Gap" = "Gap";
+
+/* Contact Image, settings, header for the font section */
+"Font" = "Font";
+
+/* Contact Image, settings, label for the font size picker */
+"Size" = "Size";
+
+/* Contact Image, settings, label for the secondary font size picker */
+"Secondary size" = "Secondary size";
+
+/* Contact Image, settings, label for the font tracking picker (tracking - horizontal spacing between letters) */
+"Tracking" = "Tracking";
+
+/* Contact Image, settings, label for the font weight picker */
+"Weight" = "Weight";
+
+/* Contact Image, settings, label for the dark-mode switcher */
+"Dark mode" = "Dark mode";
+
+/* Contact Image, settings, tighter font tracking */
+"TighterFontTracking" = "Tighter";
+
+/* Contact Image, settings, tight font tracking */
+"TightFontTracking" = "Tight";
+
+/* Contact Image, settings, normal font tracking */
+"NormalFontTracking" = "Normal";
+
+/* Contact Image, settings, wide font tracking */
+"WideFontTracking" = "Wide";
+
+/* Contact Image, settings, light font weight */
+"LightFontWeight" = "Light";
+
+/* Contact Image, settings, regular font weight */
+"RegularFontWeight" = "Regular";
+
+/* Contact Image, settings, medium font weight */
+"MediumFontWeight" = "Medium";
+
+/* Contact Image, settings, semibold font weight */
+"SemiboldFontWeight" = "Semibold";
+
+/* Contact Image, settings, bold font weight */
+"BoldFontWeight" = "Bold";
+
+/* Contact Image, settings, black font weight (black is "very bold") */
+"BlackFontWeight" = "Black";
+
+/* Contact Image, settings, don't display any value */
+"NoneContactValue" = "None";
+
+/* Contact Image, settings, Glucose (data to display) */
+"GlucoseContactValue" = "Glucose";
+
+/* Contact Image, settings, Eventual BG (data to display) */
+"EventualBGContactValue" = "Eventual BG";
+
+/* Contact Image, settings, Delta (data to display) */
+"DeltaContactValue" = "Delta";
+
+/* Contact Image, settings, Trend (data to display) */
+"TrendContactValue" = "Trend";
+
+/* Contact Image, settings, Last loop time (data to display) */
+"LastLoopTimeContactValue" = "Last loop time";
+
+/* Contact Image, settings, COB (data to display) */
+"COBContactValue" = "COB";
+
+/* Contact Image, settings, IOB (data to display) */
+"IOBContactValue" = "IOB";
+
+/* Contact Image, settings, loop status (data to display) */
+"LoopStatusContactValue" = "Loop status";
+
+/* ---------------------------------------------------------------------------------------------------------------
   Infotexts from openaps.docs and androidaps.docs
   Trio
 */

+ 152 - 1
FreeAPS/Sources/Localizations/Main/ru.lproj/Localizable.strings

@@ -1855,8 +1855,159 @@ Enact a temp Basal or a temp target */
 /* Smoothing of CGM readings */
 "Smooth Glucose Value" = "Сглаживать значения глюкозы";
 
-/* -----------------------------------------------------------------------------------------------------------
+/* --------------------------------------------------------  Contact Image --------------------------------------*/
 
+/* Contact Image, contacts list - prefix before the contact given name */
+"Contact" = "Contact";
+
+/* Contact Image, settings, header of the contacts list */
+"Contacts" = "Contacts";
+
+/* Contact Image, settings, save your changes reminedr */
+"Don't forget to save your changes." = "Don't forget to save your changes.";
+
+/* Contact Image, settings, message about need to access contacts */
+"Trio needs access to your contacts for this feature to work" = "Trio needs access to your contacts for this feature to work";
+
+/* Contact Image, settings, button to grant access to contacts */
+"Grant Trio access to contacts" = "Grant Trio access to contacts";
+
+/* Contact Image, settings, message when access to contacts denied */
+"Access to contacts denied" = "Access to contacts denied";
+
+/* Contact Image, settings, message when access to contacts restricted */
+"Access to contacts is restricted (parental control?)" = "Access to contacts is restricted (parental control?)";
+
+/* Contact Image, settings, message when access to contacts is in unknown state */
+"Access to contacts - unknown state" = "Access to contacts - unknown state";
+
+/* Contact Image, settings, short description of the feature */
+"A Contact Image can be used to get live updates from Trio to your Apple Watch Contact complication and/or your iPhone Contact widget." = "A Contact Image can be used to get live updates from Trio to your Apple Watch Contact complication and/or your iPhone Contact widget.";
+
+/* Contact Image, settings, main header */
+"Contact Image" = "Contact Image";
+
+/* Contact Image, settings, label for the layout picker */
+"Layout" = "Layout";
+
+/* Contact Image, settings, single layout name */
+"Single" = "Single";
+
+/* Contact Image, settings, split layout name */
+"Split" = "Split";
+
+/* Contact Image, settings, outer ring - don't show */
+"DontShowRing" = "Don't show";
+
+/* Contact Image, settings, outer ring - Loop status */
+"LoopStatusRing" = "Loop status";
+
+/* Contact Image, settings, outer ring - IOB */
+"IOBRing" = "IOB";
+
+/* Contact Image, settings, outer ring - COB */
+"COBRing" = "COB";
+
+/* Contact Image, settings, outer ring - IOB+COB */
+"IOB+COBRing" = "IOB+COB";
+
+/* Contact Image, settings, label for the primary value picker */
+"Primary" = "Primary";
+
+/* Contact Image, settings, label for the top value picker */
+"Top" = "Top";
+
+/* Contact Image, settings, label for the bottom value picker */
+"Bottom" = "Bottom";
+
+/* Contact Image, settings, header for the outer ring section */
+"Ring" = "Ring";
+
+/* Contact Image, settings, label for the outer ring picker */
+"Outer" = "Outer";
+
+/* Contact Image, settings, label for the ring width picker */
+"Width" = "Width";
+
+/* Contact Image, settings, label for the ring gap picker */
+"Gap" = "Gap";
+
+/* Contact Image, settings, header for the font section */
+"Font" = "Font";
+
+/* Contact Image, settings, label for the font size picker */
+"Size" = "Size";
+
+/* Contact Image, settings, label for the secondary font size picker */
+"Secondary size" = "Secondary size";
+
+/* Contact Image, settings, label for the font tracking picker (tracking - horizontal spacing between letters) */
+"Tracking" = "Tracking";
+
+/* Contact Image, settings, label for the font weight picker */
+"Weight" = "Weight";
+
+/* Contact Image, settings, label for the dark-mode switcher */
+"Dark mode" = "Dark mode";
+
+/* Contact Image, settings, tighter font tracking */
+"TighterFontTracking" = "Tighter";
+
+/* Contact Image, settings, tight font tracking */
+"TightFontTracking" = "Tight";
+
+/* Contact Image, settings, normal font tracking */
+"NormalFontTracking" = "Normal";
+
+/* Contact Image, settings, wide font tracking */
+"WideFontTracking" = "Wide";
+
+/* Contact Image, settings, light font weight */
+"LightFontWeight" = "Light";
+
+/* Contact Image, settings, regular font weight */
+"RegularFontWeight" = "Regular";
+
+/* Contact Image, settings, medium font weight */
+"MediumFontWeight" = "Medium";
+
+/* Contact Image, settings, semibold font weight */
+"SemiboldFontWeight" = "Semibold";
+
+/* Contact Image, settings, bold font weight */
+"BoldFontWeight" = "Bold";
+
+/* Contact Image, settings, black font weight (black is "very bold") */
+"BlackFontWeight" = "Black";
+
+/* Contact Image, settings, don't display any value */
+"NoneContactValue" = "None";
+
+/* Contact Image, settings, Glucose (data to display) */
+"GlucoseContactValue" = "Glucose";
+
+/* Contact Image, settings, Eventual BG (data to display) */
+"EventualBGContactValue" = "Eventual BG";
+
+/* Contact Image, settings, Delta (data to display) */
+"DeltaContactValue" = "Delta";
+
+/* Contact Image, settings, Trend (data to display) */
+"TrendContactValue" = "Trend";
+
+/* Contact Image, settings, Last loop time (data to display) */
+"LastLoopTimeContactValue" = "Last loop time";
+
+/* Contact Image, settings, COB (data to display) */
+"COBContactValue" = "COB";
+
+/* Contact Image, settings, IOB (data to display) */
+"IOBContactValue" = "IOB";
+
+/* Contact Image, settings, loop status (data to display) */
+"LoopStatusContactValue" = "Loop status";
+
+/* ---------------------------------------------------------------------------------------------------------------
   Infotexts from openaps.docs and androidaps.docs
   Trio
 */

+ 152 - 1
FreeAPS/Sources/Localizations/Main/sk.lproj/Localizable.strings

@@ -1810,8 +1810,159 @@ Enact a temp Basal or a temp target */
 /* Smoothing of CGM readings */
 "Smooth Glucose Value" = "Hladká hodnota glukózy";
 
-/* -----------------------------------------------------------------------------------------------------------
+/* --------------------------------------------------------  Contact Image --------------------------------------*/
 
+/* Contact Image, contacts list - prefix before the contact given name */
+"Contact" = "Contact";
+
+/* Contact Image, settings, header of the contacts list */
+"Contacts" = "Contacts";
+
+/* Contact Image, settings, save your changes reminedr */
+"Don't forget to save your changes." = "Don't forget to save your changes.";
+
+/* Contact Image, settings, message about need to access contacts */
+"Trio needs access to your contacts for this feature to work" = "Trio needs access to your contacts for this feature to work";
+
+/* Contact Image, settings, button to grant access to contacts */
+"Grant Trio access to contacts" = "Grant Trio access to contacts";
+
+/* Contact Image, settings, message when access to contacts denied */
+"Access to contacts denied" = "Access to contacts denied";
+
+/* Contact Image, settings, message when access to contacts restricted */
+"Access to contacts is restricted (parental control?)" = "Access to contacts is restricted (parental control?)";
+
+/* Contact Image, settings, message when access to contacts is in unknown state */
+"Access to contacts - unknown state" = "Access to contacts - unknown state";
+
+/* Contact Image, settings, short description of the feature */
+"A Contact Image can be used to get live updates from Trio to your Apple Watch Contact complication and/or your iPhone Contact widget." = "A Contact Image can be used to get live updates from Trio to your Apple Watch Contact complication and/or your iPhone Contact widget.";
+
+/* Contact Image, settings, main header */
+"Contact Image" = "Contact Image";
+
+/* Contact Image, settings, label for the layout picker */
+"Layout" = "Layout";
+
+/* Contact Image, settings, single layout name */
+"Single" = "Single";
+
+/* Contact Image, settings, split layout name */
+"Split" = "Split";
+
+/* Contact Image, settings, outer ring - don't show */
+"DontShowRing" = "Don't show";
+
+/* Contact Image, settings, outer ring - Loop status */
+"LoopStatusRing" = "Loop status";
+
+/* Contact Image, settings, outer ring - IOB */
+"IOBRing" = "IOB";
+
+/* Contact Image, settings, outer ring - COB */
+"COBRing" = "COB";
+
+/* Contact Image, settings, outer ring - IOB+COB */
+"IOB+COBRing" = "IOB+COB";
+
+/* Contact Image, settings, label for the primary value picker */
+"Primary" = "Primary";
+
+/* Contact Image, settings, label for the top value picker */
+"Top" = "Top";
+
+/* Contact Image, settings, label for the bottom value picker */
+"Bottom" = "Bottom";
+
+/* Contact Image, settings, header for the outer ring section */
+"Ring" = "Ring";
+
+/* Contact Image, settings, label for the outer ring picker */
+"Outer" = "Outer";
+
+/* Contact Image, settings, label for the ring width picker */
+"Width" = "Width";
+
+/* Contact Image, settings, label for the ring gap picker */
+"Gap" = "Gap";
+
+/* Contact Image, settings, header for the font section */
+"Font" = "Font";
+
+/* Contact Image, settings, label for the font size picker */
+"Size" = "Size";
+
+/* Contact Image, settings, label for the secondary font size picker */
+"Secondary size" = "Secondary size";
+
+/* Contact Image, settings, label for the font tracking picker (tracking - horizontal spacing between letters) */
+"Tracking" = "Tracking";
+
+/* Contact Image, settings, label for the font weight picker */
+"Weight" = "Weight";
+
+/* Contact Image, settings, label for the dark-mode switcher */
+"Dark mode" = "Dark mode";
+
+/* Contact Image, settings, tighter font tracking */
+"TighterFontTracking" = "Tighter";
+
+/* Contact Image, settings, tight font tracking */
+"TightFontTracking" = "Tight";
+
+/* Contact Image, settings, normal font tracking */
+"NormalFontTracking" = "Normal";
+
+/* Contact Image, settings, wide font tracking */
+"WideFontTracking" = "Wide";
+
+/* Contact Image, settings, light font weight */
+"LightFontWeight" = "Light";
+
+/* Contact Image, settings, regular font weight */
+"RegularFontWeight" = "Regular";
+
+/* Contact Image, settings, medium font weight */
+"MediumFontWeight" = "Medium";
+
+/* Contact Image, settings, semibold font weight */
+"SemiboldFontWeight" = "Semibold";
+
+/* Contact Image, settings, bold font weight */
+"BoldFontWeight" = "Bold";
+
+/* Contact Image, settings, black font weight (black is "very bold") */
+"BlackFontWeight" = "Black";
+
+/* Contact Image, settings, don't display any value */
+"NoneContactValue" = "None";
+
+/* Contact Image, settings, Glucose (data to display) */
+"GlucoseContactValue" = "Glucose";
+
+/* Contact Image, settings, Eventual BG (data to display) */
+"EventualBGContactValue" = "Eventual BG";
+
+/* Contact Image, settings, Delta (data to display) */
+"DeltaContactValue" = "Delta";
+
+/* Contact Image, settings, Trend (data to display) */
+"TrendContactValue" = "Trend";
+
+/* Contact Image, settings, Last loop time (data to display) */
+"LastLoopTimeContactValue" = "Last loop time";
+
+/* Contact Image, settings, COB (data to display) */
+"COBContactValue" = "COB";
+
+/* Contact Image, settings, IOB (data to display) */
+"IOBContactValue" = "IOB";
+
+/* Contact Image, settings, loop status (data to display) */
+"LoopStatusContactValue" = "Loop status";
+
+/* ---------------------------------------------------------------------------------------------------------------
   Infotexts from openaps.docs and androidaps.docs
   Trio
 */

+ 152 - 1
FreeAPS/Sources/Localizations/Main/sv.lproj/Localizable.strings

@@ -1852,8 +1852,159 @@ Enact a temp Basal or a temp target */
 /* Smoothing of CGM readings */
 "Smooth Glucose Value" = "Utjämna blodsocker från sensor";
 
-/* -----------------------------------------------------------------------------------------------------------
+/* --------------------------------------------------------  Contact Image --------------------------------------*/
 
+/* Contact Image, contacts list - prefix before the contact given name */
+"Contact" = "Contact";
+
+/* Contact Image, settings, header of the contacts list */
+"Contacts" = "Contacts";
+
+/* Contact Image, settings, save your changes reminedr */
+"Don't forget to save your changes." = "Don't forget to save your changes.";
+
+/* Contact Image, settings, message about need to access contacts */
+"Trio needs access to your contacts for this feature to work" = "Trio needs access to your contacts for this feature to work";
+
+/* Contact Image, settings, button to grant access to contacts */
+"Grant Trio access to contacts" = "Grant Trio access to contacts";
+
+/* Contact Image, settings, message when access to contacts denied */
+"Access to contacts denied" = "Access to contacts denied";
+
+/* Contact Image, settings, message when access to contacts restricted */
+"Access to contacts is restricted (parental control?)" = "Access to contacts is restricted (parental control?)";
+
+/* Contact Image, settings, message when access to contacts is in unknown state */
+"Access to contacts - unknown state" = "Access to contacts - unknown state";
+
+/* Contact Image, settings, short description of the feature */
+"A Contact Image can be used to get live updates from Trio to your Apple Watch Contact complication and/or your iPhone Contact widget." = "A Contact Image can be used to get live updates from Trio to your Apple Watch Contact complication and/or your iPhone Contact widget.";
+
+/* Contact Image, settings, main header */
+"Contact Image" = "Contact Image";
+
+/* Contact Image, settings, label for the layout picker */
+"Layout" = "Layout";
+
+/* Contact Image, settings, single layout name */
+"Single" = "Single";
+
+/* Contact Image, settings, split layout name */
+"Split" = "Split";
+
+/* Contact Image, settings, outer ring - don't show */
+"DontShowRing" = "Don't show";
+
+/* Contact Image, settings, outer ring - Loop status */
+"LoopStatusRing" = "Loop status";
+
+/* Contact Image, settings, outer ring - IOB */
+"IOBRing" = "IOB";
+
+/* Contact Image, settings, outer ring - COB */
+"COBRing" = "COB";
+
+/* Contact Image, settings, outer ring - IOB+COB */
+"IOB+COBRing" = "IOB+COB";
+
+/* Contact Image, settings, label for the primary value picker */
+"Primary" = "Primary";
+
+/* Contact Image, settings, label for the top value picker */
+"Top" = "Top";
+
+/* Contact Image, settings, label for the bottom value picker */
+"Bottom" = "Bottom";
+
+/* Contact Image, settings, header for the outer ring section */
+"Ring" = "Ring";
+
+/* Contact Image, settings, label for the outer ring picker */
+"Outer" = "Outer";
+
+/* Contact Image, settings, label for the ring width picker */
+"Width" = "Width";
+
+/* Contact Image, settings, label for the ring gap picker */
+"Gap" = "Gap";
+
+/* Contact Image, settings, header for the font section */
+"Font" = "Font";
+
+/* Contact Image, settings, label for the font size picker */
+"Size" = "Size";
+
+/* Contact Image, settings, label for the secondary font size picker */
+"Secondary size" = "Secondary size";
+
+/* Contact Image, settings, label for the font tracking picker (tracking - horizontal spacing between letters) */
+"Tracking" = "Tracking";
+
+/* Contact Image, settings, label for the font weight picker */
+"Weight" = "Weight";
+
+/* Contact Image, settings, label for the dark-mode switcher */
+"Dark mode" = "Dark mode";
+
+/* Contact Image, settings, tighter font tracking */
+"TighterFontTracking" = "Tighter";
+
+/* Contact Image, settings, tight font tracking */
+"TightFontTracking" = "Tight";
+
+/* Contact Image, settings, normal font tracking */
+"NormalFontTracking" = "Normal";
+
+/* Contact Image, settings, wide font tracking */
+"WideFontTracking" = "Wide";
+
+/* Contact Image, settings, light font weight */
+"LightFontWeight" = "Light";
+
+/* Contact Image, settings, regular font weight */
+"RegularFontWeight" = "Regular";
+
+/* Contact Image, settings, medium font weight */
+"MediumFontWeight" = "Medium";
+
+/* Contact Image, settings, semibold font weight */
+"SemiboldFontWeight" = "Semibold";
+
+/* Contact Image, settings, bold font weight */
+"BoldFontWeight" = "Bold";
+
+/* Contact Image, settings, black font weight (black is "very bold") */
+"BlackFontWeight" = "Black";
+
+/* Contact Image, settings, don't display any value */
+"NoneContactValue" = "None";
+
+/* Contact Image, settings, Glucose (data to display) */
+"GlucoseContactValue" = "Glucose";
+
+/* Contact Image, settings, Eventual BG (data to display) */
+"EventualBGContactValue" = "Eventual BG";
+
+/* Contact Image, settings, Delta (data to display) */
+"DeltaContactValue" = "Delta";
+
+/* Contact Image, settings, Trend (data to display) */
+"TrendContactValue" = "Trend";
+
+/* Contact Image, settings, Last loop time (data to display) */
+"LastLoopTimeContactValue" = "Last loop time";
+
+/* Contact Image, settings, COB (data to display) */
+"COBContactValue" = "COB";
+
+/* Contact Image, settings, IOB (data to display) */
+"IOBContactValue" = "IOB";
+
+/* Contact Image, settings, loop status (data to display) */
+"LoopStatusContactValue" = "Loop status";
+
+/* ---------------------------------------------------------------------------------------------------------------
   Infotexts from openaps.docs and androidaps.docs
   Trio
 */

+ 152 - 1
FreeAPS/Sources/Localizations/Main/tr.lproj/Localizable.strings

@@ -1856,8 +1856,159 @@ Enact a temp Basal or a temp target */
 /* Smoothing of CGM readings */
 "Smooth Glucose Value" = "Smooth Glucose Value";
 
-/* -----------------------------------------------------------------------------------------------------------
+/* --------------------------------------------------------  Contact Image --------------------------------------*/
 
+/* Contact Image, contacts list - prefix before the contact given name */
+"Contact" = "Contact";
+
+/* Contact Image, settings, header of the contacts list */
+"Contacts" = "Contacts";
+
+/* Contact Image, settings, save your changes reminedr */
+"Don't forget to save your changes." = "Don't forget to save your changes.";
+
+/* Contact Image, settings, message about need to access contacts */
+"Trio needs access to your contacts for this feature to work" = "Trio needs access to your contacts for this feature to work";
+
+/* Contact Image, settings, button to grant access to contacts */
+"Grant Trio access to contacts" = "Grant Trio access to contacts";
+
+/* Contact Image, settings, message when access to contacts denied */
+"Access to contacts denied" = "Access to contacts denied";
+
+/* Contact Image, settings, message when access to contacts restricted */
+"Access to contacts is restricted (parental control?)" = "Access to contacts is restricted (parental control?)";
+
+/* Contact Image, settings, message when access to contacts is in unknown state */
+"Access to contacts - unknown state" = "Access to contacts - unknown state";
+
+/* Contact Image, settings, short description of the feature */
+"A Contact Image can be used to get live updates from Trio to your Apple Watch Contact complication and/or your iPhone Contact widget." = "A Contact Image can be used to get live updates from Trio to your Apple Watch Contact complication and/or your iPhone Contact widget.";
+
+/* Contact Image, settings, main header */
+"Contact Image" = "Contact Image";
+
+/* Contact Image, settings, label for the layout picker */
+"Layout" = "Layout";
+
+/* Contact Image, settings, single layout name */
+"Single" = "Single";
+
+/* Contact Image, settings, split layout name */
+"Split" = "Split";
+
+/* Contact Image, settings, outer ring - don't show */
+"DontShowRing" = "Don't show";
+
+/* Contact Image, settings, outer ring - Loop status */
+"LoopStatusRing" = "Loop status";
+
+/* Contact Image, settings, outer ring - IOB */
+"IOBRing" = "IOB";
+
+/* Contact Image, settings, outer ring - COB */
+"COBRing" = "COB";
+
+/* Contact Image, settings, outer ring - IOB+COB */
+"IOB+COBRing" = "IOB+COB";
+
+/* Contact Image, settings, label for the primary value picker */
+"Primary" = "Primary";
+
+/* Contact Image, settings, label for the top value picker */
+"Top" = "Top";
+
+/* Contact Image, settings, label for the bottom value picker */
+"Bottom" = "Bottom";
+
+/* Contact Image, settings, header for the outer ring section */
+"Ring" = "Ring";
+
+/* Contact Image, settings, label for the outer ring picker */
+"Outer" = "Outer";
+
+/* Contact Image, settings, label for the ring width picker */
+"Width" = "Width";
+
+/* Contact Image, settings, label for the ring gap picker */
+"Gap" = "Gap";
+
+/* Contact Image, settings, header for the font section */
+"Font" = "Font";
+
+/* Contact Image, settings, label for the font size picker */
+"Size" = "Size";
+
+/* Contact Image, settings, label for the secondary font size picker */
+"Secondary size" = "Secondary size";
+
+/* Contact Image, settings, label for the font tracking picker (tracking - horizontal spacing between letters) */
+"Tracking" = "Tracking";
+
+/* Contact Image, settings, label for the font weight picker */
+"Weight" = "Weight";
+
+/* Contact Image, settings, label for the dark-mode switcher */
+"Dark mode" = "Dark mode";
+
+/* Contact Image, settings, tighter font tracking */
+"TighterFontTracking" = "Tighter";
+
+/* Contact Image, settings, tight font tracking */
+"TightFontTracking" = "Tight";
+
+/* Contact Image, settings, normal font tracking */
+"NormalFontTracking" = "Normal";
+
+/* Contact Image, settings, wide font tracking */
+"WideFontTracking" = "Wide";
+
+/* Contact Image, settings, light font weight */
+"LightFontWeight" = "Light";
+
+/* Contact Image, settings, regular font weight */
+"RegularFontWeight" = "Regular";
+
+/* Contact Image, settings, medium font weight */
+"MediumFontWeight" = "Medium";
+
+/* Contact Image, settings, semibold font weight */
+"SemiboldFontWeight" = "Semibold";
+
+/* Contact Image, settings, bold font weight */
+"BoldFontWeight" = "Bold";
+
+/* Contact Image, settings, black font weight (black is "very bold") */
+"BlackFontWeight" = "Black";
+
+/* Contact Image, settings, don't display any value */
+"NoneContactValue" = "None";
+
+/* Contact Image, settings, Glucose (data to display) */
+"GlucoseContactValue" = "Glucose";
+
+/* Contact Image, settings, Eventual BG (data to display) */
+"EventualBGContactValue" = "Eventual BG";
+
+/* Contact Image, settings, Delta (data to display) */
+"DeltaContactValue" = "Delta";
+
+/* Contact Image, settings, Trend (data to display) */
+"TrendContactValue" = "Trend";
+
+/* Contact Image, settings, Last loop time (data to display) */
+"LastLoopTimeContactValue" = "Last loop time";
+
+/* Contact Image, settings, COB (data to display) */
+"COBContactValue" = "COB";
+
+/* Contact Image, settings, IOB (data to display) */
+"IOBContactValue" = "IOB";
+
+/* Contact Image, settings, loop status (data to display) */
+"LoopStatusContactValue" = "Loop status";
+
+/* ---------------------------------------------------------------------------------------------------------------
   Infotexts from openaps.docs and androidaps.docs
   Trio
 */

+ 152 - 1
FreeAPS/Sources/Localizations/Main/uk.lproj/Localizable.strings

@@ -1852,8 +1852,159 @@ Enact a temp Basal or a temp target */
 /* Smoothing of CGM readings */
 "Smooth Glucose Value" = "Гладке Значення Глюкози";
 
-/* -----------------------------------------------------------------------------------------------------------
+/* --------------------------------------------------------  Contact Image --------------------------------------*/
 
+/* Contact Image, contacts list - prefix before the contact given name */
+"Contact" = "Contact";
+
+/* Contact Image, settings, header of the contacts list */
+"Contacts" = "Contacts";
+
+/* Contact Image, settings, save your changes reminedr */
+"Don't forget to save your changes." = "Don't forget to save your changes.";
+
+/* Contact Image, settings, message about need to access contacts */
+"Trio needs access to your contacts for this feature to work" = "Trio needs access to your contacts for this feature to work";
+
+/* Contact Image, settings, button to grant access to contacts */
+"Grant Trio access to contacts" = "Grant Trio access to contacts";
+
+/* Contact Image, settings, message when access to contacts denied */
+"Access to contacts denied" = "Access to contacts denied";
+
+/* Contact Image, settings, message when access to contacts restricted */
+"Access to contacts is restricted (parental control?)" = "Access to contacts is restricted (parental control?)";
+
+/* Contact Image, settings, message when access to contacts is in unknown state */
+"Access to contacts - unknown state" = "Access to contacts - unknown state";
+
+/* Contact Image, settings, short description of the feature */
+"A Contact Image can be used to get live updates from Trio to your Apple Watch Contact complication and/or your iPhone Contact widget." = "A Contact Image can be used to get live updates from Trio to your Apple Watch Contact complication and/or your iPhone Contact widget.";
+
+/* Contact Image, settings, main header */
+"Contact Image" = "Contact Image";
+
+/* Contact Image, settings, label for the layout picker */
+"Layout" = "Layout";
+
+/* Contact Image, settings, single layout name */
+"Single" = "Single";
+
+/* Contact Image, settings, split layout name */
+"Split" = "Split";
+
+/* Contact Image, settings, outer ring - don't show */
+"DontShowRing" = "Don't show";
+
+/* Contact Image, settings, outer ring - Loop status */
+"LoopStatusRing" = "Loop status";
+
+/* Contact Image, settings, outer ring - IOB */
+"IOBRing" = "IOB";
+
+/* Contact Image, settings, outer ring - COB */
+"COBRing" = "COB";
+
+/* Contact Image, settings, outer ring - IOB+COB */
+"IOB+COBRing" = "IOB+COB";
+
+/* Contact Image, settings, label for the primary value picker */
+"Primary" = "Primary";
+
+/* Contact Image, settings, label for the top value picker */
+"Top" = "Top";
+
+/* Contact Image, settings, label for the bottom value picker */
+"Bottom" = "Bottom";
+
+/* Contact Image, settings, header for the outer ring section */
+"Ring" = "Ring";
+
+/* Contact Image, settings, label for the outer ring picker */
+"Outer" = "Outer";
+
+/* Contact Image, settings, label for the ring width picker */
+"Width" = "Width";
+
+/* Contact Image, settings, label for the ring gap picker */
+"Gap" = "Gap";
+
+/* Contact Image, settings, header for the font section */
+"Font" = "Font";
+
+/* Contact Image, settings, label for the font size picker */
+"Size" = "Size";
+
+/* Contact Image, settings, label for the secondary font size picker */
+"Secondary size" = "Secondary size";
+
+/* Contact Image, settings, label for the font tracking picker (tracking - horizontal spacing between letters) */
+"Tracking" = "Tracking";
+
+/* Contact Image, settings, label for the font weight picker */
+"Weight" = "Weight";
+
+/* Contact Image, settings, label for the dark-mode switcher */
+"Dark mode" = "Dark mode";
+
+/* Contact Image, settings, tighter font tracking */
+"TighterFontTracking" = "Tighter";
+
+/* Contact Image, settings, tight font tracking */
+"TightFontTracking" = "Tight";
+
+/* Contact Image, settings, normal font tracking */
+"NormalFontTracking" = "Normal";
+
+/* Contact Image, settings, wide font tracking */
+"WideFontTracking" = "Wide";
+
+/* Contact Image, settings, light font weight */
+"LightFontWeight" = "Light";
+
+/* Contact Image, settings, regular font weight */
+"RegularFontWeight" = "Regular";
+
+/* Contact Image, settings, medium font weight */
+"MediumFontWeight" = "Medium";
+
+/* Contact Image, settings, semibold font weight */
+"SemiboldFontWeight" = "Semibold";
+
+/* Contact Image, settings, bold font weight */
+"BoldFontWeight" = "Bold";
+
+/* Contact Image, settings, black font weight (black is "very bold") */
+"BlackFontWeight" = "Black";
+
+/* Contact Image, settings, don't display any value */
+"NoneContactValue" = "None";
+
+/* Contact Image, settings, Glucose (data to display) */
+"GlucoseContactValue" = "Glucose";
+
+/* Contact Image, settings, Eventual BG (data to display) */
+"EventualBGContactValue" = "Eventual BG";
+
+/* Contact Image, settings, Delta (data to display) */
+"DeltaContactValue" = "Delta";
+
+/* Contact Image, settings, Trend (data to display) */
+"TrendContactValue" = "Trend";
+
+/* Contact Image, settings, Last loop time (data to display) */
+"LastLoopTimeContactValue" = "Last loop time";
+
+/* Contact Image, settings, COB (data to display) */
+"COBContactValue" = "COB";
+
+/* Contact Image, settings, IOB (data to display) */
+"IOBContactValue" = "IOB";
+
+/* Contact Image, settings, loop status (data to display) */
+"LoopStatusContactValue" = "Loop status";
+
+/* ---------------------------------------------------------------------------------------------------------------
   Infotexts from openaps.docs and androidaps.docs
   Trio
 */

+ 152 - 1
FreeAPS/Sources/Localizations/Main/vi.lproj/Localizable.strings

@@ -1810,8 +1810,159 @@ Enact a temp Basal or a temp target */
 /* Smoothing of CGM readings */
 "Smooth Glucose Value" = "Giá trị Glucose mịn";
 
-/* -----------------------------------------------------------------------------------------------------------
+/* --------------------------------------------------------  Contact Image --------------------------------------*/
 
+/* Contact Image, contacts list - prefix before the contact given name */
+"Contact" = "Contact";
+
+/* Contact Image, settings, header of the contacts list */
+"Contacts" = "Contacts";
+
+/* Contact Image, settings, save your changes reminedr */
+"Don't forget to save your changes." = "Don't forget to save your changes.";
+
+/* Contact Image, settings, message about need to access contacts */
+"Trio needs access to your contacts for this feature to work" = "Trio needs access to your contacts for this feature to work";
+
+/* Contact Image, settings, button to grant access to contacts */
+"Grant Trio access to contacts" = "Grant Trio access to contacts";
+
+/* Contact Image, settings, message when access to contacts denied */
+"Access to contacts denied" = "Access to contacts denied";
+
+/* Contact Image, settings, message when access to contacts restricted */
+"Access to contacts is restricted (parental control?)" = "Access to contacts is restricted (parental control?)";
+
+/* Contact Image, settings, message when access to contacts is in unknown state */
+"Access to contacts - unknown state" = "Access to contacts - unknown state";
+
+/* Contact Image, settings, short description of the feature */
+"A Contact Image can be used to get live updates from Trio to your Apple Watch Contact complication and/or your iPhone Contact widget." = "A Contact Image can be used to get live updates from Trio to your Apple Watch Contact complication and/or your iPhone Contact widget.";
+
+/* Contact Image, settings, main header */
+"Contact Image" = "Contact Image";
+
+/* Contact Image, settings, label for the layout picker */
+"Layout" = "Layout";
+
+/* Contact Image, settings, single layout name */
+"Single" = "Single";
+
+/* Contact Image, settings, split layout name */
+"Split" = "Split";
+
+/* Contact Image, settings, outer ring - don't show */
+"DontShowRing" = "Don't show";
+
+/* Contact Image, settings, outer ring - Loop status */
+"LoopStatusRing" = "Loop status";
+
+/* Contact Image, settings, outer ring - IOB */
+"IOBRing" = "IOB";
+
+/* Contact Image, settings, outer ring - COB */
+"COBRing" = "COB";
+
+/* Contact Image, settings, outer ring - IOB+COB */
+"IOB+COBRing" = "IOB+COB";
+
+/* Contact Image, settings, label for the primary value picker */
+"Primary" = "Primary";
+
+/* Contact Image, settings, label for the top value picker */
+"Top" = "Top";
+
+/* Contact Image, settings, label for the bottom value picker */
+"Bottom" = "Bottom";
+
+/* Contact Image, settings, header for the outer ring section */
+"Ring" = "Ring";
+
+/* Contact Image, settings, label for the outer ring picker */
+"Outer" = "Outer";
+
+/* Contact Image, settings, label for the ring width picker */
+"Width" = "Width";
+
+/* Contact Image, settings, label for the ring gap picker */
+"Gap" = "Gap";
+
+/* Contact Image, settings, header for the font section */
+"Font" = "Font";
+
+/* Contact Image, settings, label for the font size picker */
+"Size" = "Size";
+
+/* Contact Image, settings, label for the secondary font size picker */
+"Secondary size" = "Secondary size";
+
+/* Contact Image, settings, label for the font tracking picker (tracking - horizontal spacing between letters) */
+"Tracking" = "Tracking";
+
+/* Contact Image, settings, label for the font weight picker */
+"Weight" = "Weight";
+
+/* Contact Image, settings, label for the dark-mode switcher */
+"Dark mode" = "Dark mode";
+
+/* Contact Image, settings, tighter font tracking */
+"TighterFontTracking" = "Tighter";
+
+/* Contact Image, settings, tight font tracking */
+"TightFontTracking" = "Tight";
+
+/* Contact Image, settings, normal font tracking */
+"NormalFontTracking" = "Normal";
+
+/* Contact Image, settings, wide font tracking */
+"WideFontTracking" = "Wide";
+
+/* Contact Image, settings, light font weight */
+"LightFontWeight" = "Light";
+
+/* Contact Image, settings, regular font weight */
+"RegularFontWeight" = "Regular";
+
+/* Contact Image, settings, medium font weight */
+"MediumFontWeight" = "Medium";
+
+/* Contact Image, settings, semibold font weight */
+"SemiboldFontWeight" = "Semibold";
+
+/* Contact Image, settings, bold font weight */
+"BoldFontWeight" = "Bold";
+
+/* Contact Image, settings, black font weight (black is "very bold") */
+"BlackFontWeight" = "Black";
+
+/* Contact Image, settings, don't display any value */
+"NoneContactValue" = "None";
+
+/* Contact Image, settings, Glucose (data to display) */
+"GlucoseContactValue" = "Glucose";
+
+/* Contact Image, settings, Eventual BG (data to display) */
+"EventualBGContactValue" = "Eventual BG";
+
+/* Contact Image, settings, Delta (data to display) */
+"DeltaContactValue" = "Delta";
+
+/* Contact Image, settings, Trend (data to display) */
+"TrendContactValue" = "Trend";
+
+/* Contact Image, settings, Last loop time (data to display) */
+"LastLoopTimeContactValue" = "Last loop time";
+
+/* Contact Image, settings, COB (data to display) */
+"COBContactValue" = "COB";
+
+/* Contact Image, settings, IOB (data to display) */
+"IOBContactValue" = "IOB";
+
+/* Contact Image, settings, loop status (data to display) */
+"LoopStatusContactValue" = "Loop status";
+
+/* ---------------------------------------------------------------------------------------------------------------
   Infotexts from openaps.docs and androidaps.docs
   Trio
 */

+ 152 - 1
FreeAPS/Sources/Localizations/Main/zh-Hans.lproj/Localizable.strings

@@ -1852,8 +1852,159 @@ Enact a temp Basal or a temp target */
 /* Smoothing of CGM readings */
 "Smooth Glucose Value" = "Smooth Glucose Value";
 
-/* -----------------------------------------------------------------------------------------------------------
+/* --------------------------------------------------------  Contact Image --------------------------------------*/
 
+/* Contact Image, contacts list - prefix before the contact given name */
+"Contact" = "Contact";
+
+/* Contact Image, settings, header of the contacts list */
+"Contacts" = "Contacts";
+
+/* Contact Image, settings, save your changes reminedr */
+"Don't forget to save your changes." = "Don't forget to save your changes.";
+
+/* Contact Image, settings, message about need to access contacts */
+"Trio needs access to your contacts for this feature to work" = "Trio needs access to your contacts for this feature to work";
+
+/* Contact Image, settings, button to grant access to contacts */
+"Grant Trio access to contacts" = "Grant Trio access to contacts";
+
+/* Contact Image, settings, message when access to contacts denied */
+"Access to contacts denied" = "Access to contacts denied";
+
+/* Contact Image, settings, message when access to contacts restricted */
+"Access to contacts is restricted (parental control?)" = "Access to contacts is restricted (parental control?)";
+
+/* Contact Image, settings, message when access to contacts is in unknown state */
+"Access to contacts - unknown state" = "Access to contacts - unknown state";
+
+/* Contact Image, settings, short description of the feature */
+"A Contact Image can be used to get live updates from Trio to your Apple Watch Contact complication and/or your iPhone Contact widget." = "A Contact Image can be used to get live updates from Trio to your Apple Watch Contact complication and/or your iPhone Contact widget.";
+
+/* Contact Image, settings, main header */
+"Contact Image" = "Contact Image";
+
+/* Contact Image, settings, label for the layout picker */
+"Layout" = "Layout";
+
+/* Contact Image, settings, single layout name */
+"Single" = "Single";
+
+/* Contact Image, settings, split layout name */
+"Split" = "Split";
+
+/* Contact Image, settings, outer ring - don't show */
+"DontShowRing" = "Don't show";
+
+/* Contact Image, settings, outer ring - Loop status */
+"LoopStatusRing" = "Loop status";
+
+/* Contact Image, settings, outer ring - IOB */
+"IOBRing" = "IOB";
+
+/* Contact Image, settings, outer ring - COB */
+"COBRing" = "COB";
+
+/* Contact Image, settings, outer ring - IOB+COB */
+"IOB+COBRing" = "IOB+COB";
+
+/* Contact Image, settings, label for the primary value picker */
+"Primary" = "Primary";
+
+/* Contact Image, settings, label for the top value picker */
+"Top" = "Top";
+
+/* Contact Image, settings, label for the bottom value picker */
+"Bottom" = "Bottom";
+
+/* Contact Image, settings, header for the outer ring section */
+"Ring" = "Ring";
+
+/* Contact Image, settings, label for the outer ring picker */
+"Outer" = "Outer";
+
+/* Contact Image, settings, label for the ring width picker */
+"Width" = "Width";
+
+/* Contact Image, settings, label for the ring gap picker */
+"Gap" = "Gap";
+
+/* Contact Image, settings, header for the font section */
+"Font" = "Font";
+
+/* Contact Image, settings, label for the font size picker */
+"Size" = "Size";
+
+/* Contact Image, settings, label for the secondary font size picker */
+"Secondary size" = "Secondary size";
+
+/* Contact Image, settings, label for the font tracking picker (tracking - horizontal spacing between letters) */
+"Tracking" = "Tracking";
+
+/* Contact Image, settings, label for the font weight picker */
+"Weight" = "Weight";
+
+/* Contact Image, settings, label for the dark-mode switcher */
+"Dark mode" = "Dark mode";
+
+/* Contact Image, settings, tighter font tracking */
+"TighterFontTracking" = "Tighter";
+
+/* Contact Image, settings, tight font tracking */
+"TightFontTracking" = "Tight";
+
+/* Contact Image, settings, normal font tracking */
+"NormalFontTracking" = "Normal";
+
+/* Contact Image, settings, wide font tracking */
+"WideFontTracking" = "Wide";
+
+/* Contact Image, settings, light font weight */
+"LightFontWeight" = "Light";
+
+/* Contact Image, settings, regular font weight */
+"RegularFontWeight" = "Regular";
+
+/* Contact Image, settings, medium font weight */
+"MediumFontWeight" = "Medium";
+
+/* Contact Image, settings, semibold font weight */
+"SemiboldFontWeight" = "Semibold";
+
+/* Contact Image, settings, bold font weight */
+"BoldFontWeight" = "Bold";
+
+/* Contact Image, settings, black font weight (black is "very bold") */
+"BlackFontWeight" = "Black";
+
+/* Contact Image, settings, don't display any value */
+"NoneContactValue" = "None";
+
+/* Contact Image, settings, Glucose (data to display) */
+"GlucoseContactValue" = "Glucose";
+
+/* Contact Image, settings, Eventual BG (data to display) */
+"EventualBGContactValue" = "Eventual BG";
+
+/* Contact Image, settings, Delta (data to display) */
+"DeltaContactValue" = "Delta";
+
+/* Contact Image, settings, Trend (data to display) */
+"TrendContactValue" = "Trend";
+
+/* Contact Image, settings, Last loop time (data to display) */
+"LastLoopTimeContactValue" = "Last loop time";
+
+/* Contact Image, settings, COB (data to display) */
+"COBContactValue" = "COB";
+
+/* Contact Image, settings, IOB (data to display) */
+"IOBContactValue" = "IOB";
+
+/* Contact Image, settings, loop status (data to display) */
+"LoopStatusContactValue" = "Loop status";
+
+/* ---------------------------------------------------------------------------------------------------------------
   Infotexts from openaps.docs and androidaps.docs
   Trio
 */

+ 79 - 0
FreeAPS/Sources/Models/ContactTrickEntry.swift

@@ -0,0 +1,79 @@
+
+struct ContactTrickEntry: JSON, Equatable, Hashable {
+    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 darkMode: Bool = true
+    var ringWidth: Int = 7
+    var ringGap: Int = 2
+    var fontSize: Int = 300
+    var secondaryFontSize: Int = 250
+    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 layout
+        case ring1
+        case primary
+        case top
+        case bottom
+        case contactId
+        case darkMode
+        case ringWidth
+        case ringGap
+        case fontSize
+        case secondaryFontSize
+        case fontName
+        case fontWeight
+        case fontTracking
+    }
+
+    init(from decoder: Decoder) throws {
+        let container = try decoder.container(keyedBy: CodingKeys.self)
+        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 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) ?? 300
+        let secondaryFontSize = try container.decodeIfPresent(Int.self, forKey: .secondaryFontSize) ?? 250
+        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(
+            layout: layout,
+            ring1: ring1,
+            primary: primary,
+            top: top,
+            bottom: bottom,
+            contactId: contactId,
+            darkMode: darkMode,
+            ringWidth: ringWidth,
+            ringGap: ringGap,
+            fontSize: fontSize,
+            secondaryFontSize: secondaryFontSize,
+            fontName: fontName,
+            fontWeight: fontWeight,
+            fontTracking: fontTracking
+        )
+    }
+}

+ 32 - 0
FreeAPS/Sources/Models/FontTracking.swift

@@ -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("TighterFontTracking", comment: "")
+        case .tight:
+            NSLocalizedString("TightFontTracking", comment: "")
+        case .normal:
+            NSLocalizedString("NormalFontTracking", comment: "")
+        case .wide:
+            NSLocalizedString("WideFontTracking", comment: "")
+        }
+    }
+
+    var value: Double {
+        switch self {
+        case .tighter: -0.07
+        case .tight: -0.04
+        case .normal: 0
+        case .wide: 0.04
+        }
+    }
+}

+ 29 - 0
FreeAPS/Sources/Models/FontWeight.swift

@@ -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("LightFontWeight", comment: "")
+        case .regular:
+            return NSLocalizedString("RegularFontWeight", comment: "")
+        case .medium:
+            return NSLocalizedString("MediumFontWeight", comment: "")
+        case .semibold:
+            return NSLocalizedString("SemiboldFontWeight", comment: "")
+        case .bold:
+            return NSLocalizedString("BoldFontWeight", comment: "")
+        case .black:
+            return NSLocalizedString("BlackFontWeight", comment: "")
+        }
+    }
+}

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

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

+ 30 - 0
FreeAPS/Sources/Modules/ContactTrick/ContactTrickDataFlow.swift

@@ -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<[ContactTrickEntry], Error>
+}

+ 25 - 0
FreeAPS/Sources/Modules/ContactTrick/ContactTrickProvider.swift

@@ -0,0 +1,25 @@
+import Combine
+import Foundation
+
+extension ContactTrick {
+    final class Provider: BaseProvider, ContactTrickProvider {
+        private let processQueue = DispatchQueue(label: "ContactTrickProvider.processQueue")
+
+        var contacts: [ContactTrickEntry] {
+            contactTrickManager.currentContacts
+        }
+
+        func saveContacts(_ contacts: [ContactTrickEntry]) -> AnyPublisher<[ContactTrickEntry], Error> {
+            Future { promise in
+                self.contactTrickManager.updateContacts(contacts: contacts) { result in
+                    switch result {
+                    case let .success(updated):
+                        promise(.success(updated))
+                    case let .failure(error):
+                        promise(.failure(error))
+                    }
+                }
+            }.eraseToAnyPublisher()
+        }
+    }
+}

+ 136 - 0
FreeAPS/Sources/Modules/ContactTrick/ContactTrickStateModel.swift

@@ -0,0 +1,136 @@
+import ConnectIQ
+import SwiftUI
+
+enum ContactTrickValue: String, JSON, CaseIterable, Identifiable, Codable {
+    var id: String { rawValue }
+    case none
+    case glucose
+    case eventualBG
+    case delta
+    case trend
+    case lastLoopDate
+    case cob
+    case iob
+    case ring
+
+    var displayName: String {
+        switch self {
+        case .none:
+            return NSLocalizedString("NoneContactValue", comment: "")
+        case .glucose:
+            return NSLocalizedString("GlucoseContactValue", comment: "")
+        case .eventualBG:
+            return NSLocalizedString("EventualBGContactValue", comment: "")
+        case .delta:
+            return NSLocalizedString("DeltaContactValue", comment: "")
+        case .trend:
+            return NSLocalizedString("TrendContactValue", comment: "")
+        case .lastLoopDate:
+            return NSLocalizedString("LastLoopTimeContactValue", comment: "")
+        case .cob:
+            return NSLocalizedString("COBContactValue", comment: "")
+        case .iob:
+            return NSLocalizedString("IOBContactValue", comment: "")
+        case .ring:
+            return NSLocalizedString("LoopStatusContactValue", comment: "")
+        }
+    }
+}
+
+enum ContactTrickLayout: String, JSON, CaseIterable, Identifiable, Codable {
+    var id: String { rawValue }
+    case single
+    case split
+
+    var displayName: String {
+        switch self {
+        case .single:
+            return NSLocalizedString("Single", comment: "")
+        case .split:
+            return NSLocalizedString("Split", comment: "")
+        }
+    }
+}
+
+enum ContactTrickLargeRing: String, JSON, CaseIterable, Identifiable, Codable {
+    var id: String { rawValue }
+    case none
+    case loop
+    case iob
+    case cob
+    case iobcob
+
+    var displayName: String {
+        switch self {
+        case .none:
+            return NSLocalizedString("DontShowRing", comment: "")
+        case .loop:
+            return NSLocalizedString("LoopStatusRing", comment: "")
+        case .iob:
+            return NSLocalizedString("IOBRing", comment: "")
+        case .cob:
+            return NSLocalizedString("COBRing", comment: "")
+        case .iobcob:
+            return NSLocalizedString("IOB+COBRing", comment: "")
+        }
+    }
+}
+
+extension ContactTrick {
+    final class StateModel: BaseStateModel<Provider> {
+        @Published private(set) var syncInProgress = false
+        @Published private(set) var items: [Item] = []
+        @Published private(set) var changed: Bool = false
+
+        var units: GlucoseUnits = .mmolL
+
+        override func subscribe() {
+            units = settingsManager.settings.units
+            items = provider.contacts.enumerated().map { index, contact in
+                Item(
+                    index: index,
+                    entry: contact
+                )
+            }
+            changed = false
+        }
+
+        func add() {
+            let newItem = Item(
+                index: items.count,
+                entry: ContactTrickEntry()
+            )
+
+            items.append(newItem)
+            changed = true
+        }
+
+        func update(_ atIndex: Int, _ value: ContactTrickEntry) {
+            items[atIndex].entry = value
+            changed = true
+        }
+
+        func remove(atOffsets: IndexSet) {
+            items.remove(atOffsets: atOffsets)
+            changed = true
+        }
+
+        func save() {
+            syncInProgress = true
+            let contacts = items.map { item -> ContactTrickEntry in
+                item.entry
+            }
+            provider.saveContacts(contacts)
+                .receive(on: DispatchQueue.main)
+                .sink { _ in
+                    self.syncInProgress = false
+                    self.changed = false
+                } receiveValue: { contacts in
+                    contacts.enumerated().forEach { index, item in
+                        self.items[index].entry = item
+                    }
+                }
+                .store(in: &lifetime)
+        }
+    }
+}

+ 383 - 0
FreeAPS/Sources/Modules/ContactTrick/View/ContactTrickRootView.swift

@@ -0,0 +1,383 @@
+import Contacts
+import ContactsUI
+import SwiftUI
+import Swinject
+
+extension ContactTrick {
+    struct RootView: BaseView {
+        let resolver: Resolver
+        @StateObject var state = StateModel()
+
+        @State private var contactStore = CNContactStore()
+        @State private var authorization = CNContactStore.authorizationStatus(for: .contacts)
+
+        var body: some View {
+            Form {
+                switch authorization {
+                case .authorized:
+                    Section(header: Text("Contacts")) {
+                        list
+                        addButton
+                    }
+                    Section(
+                        header: state.changed ?
+                            Text("Don't forget to save your changes.")
+                            .frame(maxWidth: .infinity, alignment: .center)
+                            .foregroundStyle(.primary) : nil
+                    ) {
+                        HStack {
+                            if state.syncInProgress {
+                                ProgressView().padding(.trailing, 10)
+                            }
+                            Button { state.save() }
+                            label: {
+                                Text(state.syncInProgress ? "Saving..." : "Save")
+                            }
+                            .disabled(state.syncInProgress || !state.changed)
+                            .frame(maxWidth: .infinity, alignment: .center)
+                        }
+                    }
+
+                case .notDetermined:
+                    Section {
+                        Text(
+                            "Trio needs access to your contacts for this feature to work"
+                        )
+                    }
+                    Section {
+                        Button(action: onRequestContactsAccess) {
+                            Text("Grant Trio access to contacts")
+                        }
+                    }
+
+                case .denied:
+                    Section {
+                        Text(
+                            "Access to contacts denied"
+                        )
+                    }
+
+                case .restricted:
+                    Section {
+                        Text(
+                            "Access to contacts is restricted (parental control?)"
+                        )
+                    }
+
+                @unknown default:
+                    Section {
+                        Text(
+                            "Access to contacts - unknown state"
+                        )
+                    }
+                }
+
+                Section {}
+                footer: {
+                    Text(
+                        "A Contact Image can be used to get live updates from Trio to your Apple Watch Contact complication and/or your iPhone Contact widget."
+                    )
+                    .frame(maxWidth: .infinity, alignment: .center)
+                }
+            }
+            .dynamicTypeSize(...DynamicTypeSize.xxLarge)
+            .onAppear(perform: configureView)
+            .navigationTitle("Contact Image")
+            .navigationBarTitleDisplayMode(.automatic)
+            .navigationBarItems(
+                trailing: EditButton()
+            )
+        }
+
+        private func contactSettings(for index: Int) -> some View {
+            EntryView(entry: Binding(
+                get: { state.items[index].entry },
+                set: { newValue in state.update(index, newValue) }
+            ), previewState: previewState)
+        }
+
+        var previewState: ContactTrickState {
+            let units = state.units
+
+            return ContactTrickState(
+                glucose: units == .mmolL ? "6,8" : "127",
+                trend: "↗︎",
+                delta: units == .mmolL ? "+0,3" : "+7",
+                lastLoopDate: .now,
+                iob: 6.1,
+                iobText: "6,1",
+                cob: 27.0,
+                cobText: "27",
+                eventualBG: units == .mmolL ? "8,9" : "163",
+                maxIOB: 12.0,
+                maxCOB: 120.0
+            )
+        }
+
+        private var list: some View {
+            List {
+                ForEach(state.items.indexed(), id: \.1.id) { index, item in
+                    NavigationLink(destination: contactSettings(for: index)) {
+                        EntryListView(entry: .constant(item.entry), index: .constant(index), previewState: previewState)
+                    }
+                    .moveDisabled(true)
+                }
+                .onDelete(perform: onDelete)
+            }
+        }
+
+        private var addButton: some View {
+            AnyView(Button(action: onAdd) { Text("Add") })
+        }
+
+        func onAdd() {
+            state.add()
+        }
+
+        func onRequestContactsAccess() {
+            contactStore.requestAccess(for: .contacts) { _, _ in
+                DispatchQueue.main.async {
+                    authorization = CNContactStore.authorizationStatus(for: .contacts)
+                }
+            }
+        }
+
+        private func onDelete(offsets: IndexSet) {
+            state.remove(atOffsets: offsets)
+        }
+    }
+
+    struct EntryListView: View {
+        @Binding var entry: ContactTrickEntry
+        @Binding var index: Int
+        @State private var refreshKey = UUID()
+        let previewState: ContactTrickState
+
+        var body: some View {
+            HStack {
+                Text(
+                    NSLocalizedString("Contact", comment: "") + ": " + "Trio \(index + 1)"
+                )
+                .font(.body)
+                .minimumScaleFactor(0.5)
+                .lineLimit(1)
+
+                Spacer()
+
+                VStack {
+                    GeometryReader { geometry in
+                        ZStack {
+                            Circle()
+                                .fill(entry.darkMode ? .black : .white)
+                                .foregroundColor(.white)
+                            Image(uiImage: ContactPicture.getImage(contact: entry, state: previewState))
+                                .resizable()
+                                .aspectRatio(1, contentMode: .fit)
+                                .frame(width: geometry.size.height, height: geometry.size.height)
+                                .clipShape(Circle())
+                            Circle()
+                                .stroke(lineWidth: 2)
+                                .foregroundColor(.white)
+                        }
+                        .frame(width: geometry.size.height, height: geometry.size.height)
+                    }
+                }
+                .fixedSize(horizontal: true, vertical: false)
+                .padding(.horizontal, 30)
+            }
+            .frame(maxWidth: .infinity)
+        }
+    }
+
+    struct EntryView: View {
+        @Binding var entry: ContactTrickEntry
+        @State private var availableFonts: [String]? = nil
+        let previewState: ContactTrickState
+
+        private let fontSizes: [Int] = [100, 120, 130, 140, 160, 180, 200, 225, 250, 275, 300, 350, 400]
+        private let ringWidths: [Int] = [5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
+        private let ringGaps: [Int] = [0, 1, 2, 3, 4, 5]
+
+        var body: some View {
+            Section {
+                HStack {
+                    ZStack {
+                        Circle()
+                            .fill(entry.darkMode ? .black : .white)
+                            .foregroundColor(.white)
+                        Image(uiImage: ContactPicture.getImage(contact: entry, state: previewState))
+                            .resizable()
+                            .aspectRatio(1, contentMode: .fit)
+                            .frame(width: 64, height: 64)
+                            .clipShape(Circle())
+                        Circle()
+                            .stroke(lineWidth: 2)
+                            .foregroundColor(.white)
+                    }
+                    .frame(width: 64, height: 64)
+                }
+            }
+            Form {
+                Section {
+                    Picker(
+                        selection: $entry.layout,
+                        label: Text("Layout")
+                    ) {
+                        ForEach(ContactTrickLayout.allCases) { v in
+                            Text(v.displayName).tag(v)
+                        }
+                    }
+                }
+                Section {
+                    switch entry.layout {
+                    case .single:
+                        Picker(
+                            selection: $entry.primary,
+                            label: Text("Primary")
+                        ) {
+                            ForEach(ContactTrickValue.allCases) { v in
+                                Text(v.displayName).tag(v)
+                            }
+                        }
+                        Picker(
+                            selection: $entry.top,
+                            label: Text("Top")
+                        ) {
+                            ForEach(ContactTrickValue.allCases) { v in
+                                Text(v.displayName).tag(v)
+                            }
+                        }
+                        Picker(
+                            selection: $entry.bottom,
+                            label: Text("Bottom")
+                        ) {
+                            ForEach(ContactTrickValue.allCases) { v in
+                                Text(v.displayName).tag(v)
+                            }
+                        }
+                    case .split:
+                        Picker(
+                            selection: $entry.top,
+                            label: Text("Top")
+                        ) {
+                            ForEach(ContactTrickValue.allCases) { v in
+                                Text(v.displayName).tag(v)
+                            }
+                        }
+                        Picker(
+                            selection: $entry.bottom,
+                            label: Text("Bottom")
+                        ) {
+                            ForEach(ContactTrickValue.allCases) { v in
+                                Text(v.displayName).tag(v)
+                            }
+                        }
+                    }
+                }
+
+                Section(header: Text("Ring")) {
+                    Picker(
+                        selection: $entry.ring1,
+                        label: Text("Outer")
+                    ) {
+                        ForEach(ContactTrickLargeRing.allCases) { v in
+                            Text(v.displayName).tag(v)
+                        }
+                    }
+                    Picker(
+                        selection: $entry.ringWidth,
+                        label: Text("Width")
+                    ) {
+                        ForEach(ringWidths, id: \.self) { s in
+                            Text("\(s)").tag(s)
+                        }
+                    }
+                    Picker(
+                        selection: $entry.ringGap,
+                        label: Text("Gap")
+                    ) {
+                        ForEach(ringGaps, id: \.self) { s in
+                            Text("\(s)").tag(s)
+                        }
+                    }
+                }
+
+                Section(header: Text("Font")) {
+                    if availableFonts == nil {
+                        HStack {
+                            Spacer()
+                            Button {
+                                loadFonts()
+                            } label: {
+                                Text(entry.fontName)
+                            }
+                        }
+                    } else {
+                        Picker(
+                            selection: $entry.fontName,
+                            label: EmptyView()
+                        ) {
+                            ForEach(availableFonts!, id: \.self) { f in
+                                Text(f).tag(f)
+                            }
+                        }
+                        .pickerStyle(.navigationLink)
+                        .labelsHidden()
+                    }
+                    Picker(
+                        selection: $entry.fontSize,
+                        label: Text("Size")
+                    ) {
+                        ForEach(fontSizes, id: \.self) { s in
+                            Text("\(s)").tag(s)
+                        }
+                    }
+                    Picker(
+                        selection: $entry.secondaryFontSize,
+                        label: Text("Secondary size")
+                    ) {
+                        ForEach(fontSizes, id: \.self) { s in
+                            Text("\(s)").tag(s)
+                        }
+                    }
+                    Picker(
+                        selection: $entry.fontTracking,
+                        label: Text("Tracking")
+                    ) {
+                        ForEach(FontTracking.allCases) { w in
+                            Text(w.displayName).tag(w)
+                        }
+                    }
+                    if entry.isDefaultFont() {
+                        Picker(
+                            selection: $entry.fontWeight,
+                            label: Text("Weight")
+                        ) {
+                            ForEach(FontWeight.allCases) { w in
+                                Text(w.displayName).tag(w)
+                            }
+                        }
+                    }
+                }
+                Section {
+                    Toggle("Dark mode", isOn: $entry.darkMode)
+                }
+            }
+        }
+
+        private func loadFonts() {
+            if availableFonts != nil {
+                return
+            }
+            var data = [String]()
+
+            data.append("Default Font")
+            UIFont.familyNames.forEach { family in
+                UIFont.fontNames(forFamilyName: family).forEach { font in
+                    data.append(font)
+                }
+            }
+            availableFonts = data
+        }
+    }
+}

+ 6 - 0
FreeAPS/Sources/Modules/Settings/SettingItems.swift

@@ -52,6 +52,12 @@ enum SettingItems {
             view: .watch,
             searchContents: ["Display on Watch", "Show Protein and Fat", "Confirm Bolus Faster"],
             path: ["Devices", "Smart Watch", "Apple Watch"]
+        ),
+        SettingItem(
+            title: "Contact Image",
+            view: .watch,
+            searchContents: ["Display on Watch", "Watch Complication"],
+            path: ["Devices", "Smart Watch", "Contact Image"]
         )
     ]
 

+ 10 - 5
FreeAPS/Sources/Modules/WatchConfig/View/WatchConfigRootView.swift

@@ -33,11 +33,16 @@ extension WatchConfig {
                         NavigationLink("Garmin", destination: WatchConfigGarminView(state: state))
                     }
                 ).listRowBackground(Color.chart)
-            }
-            .scrollContentBackground(.hidden).background(color)
-            .onAppear(perform: configureView)
-            .navigationTitle("Watch")
-            .navigationBarTitleDisplayMode(.automatic)
+                Section(
+                    header: Text("Complications"),
+                    content: {
+                        NavigationLink("Contact Image", destination: ContactTrick.RootView(resolver: resolver))
+                    }
+                ).listRowBackground(Color.chart) }
+                .scrollContentBackground(.hidden).background(color)
+                .onAppear(perform: configureView)
+                .navigationTitle("Watch")
+                .navigationBarTitleDisplayMode(.automatic)
         }
     }
 }

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

@@ -42,6 +42,7 @@ enum Screen: Identifiable, Hashable {
     case liveActivitySettings
     case liveActivityBottomRowSettings
     case calendarEventSettings
+    case contactTrick
     case serviceSettings
     case remoteControlConfig
     case autosensSettings
@@ -138,6 +139,8 @@ extension Screen {
             LiveActivityWidgetConfiguration(resolver: resolver, state: LiveActivitySettings.StateModel())
         case .calendarEventSettings:
             CalendarEventSettings.RootView(resolver: resolver)
+        case .contactTrick:
+            ContactTrick.RootView(resolver: resolver)
         case .serviceSettings:
             ServicesView(resolver: resolver, state: Settings.StateModel())
         case .autosensSettings:

+ 901 - 0
FreeAPS/Sources/Services/ContactTrick/ContactPicture.swift

@@ -0,0 +1,901 @@
+import Foundation
+import SwiftUI
+
+struct ContactPicture: View {
+    private enum Config {
+        static let lag: TimeInterval = 30
+    }
+
+    @Binding var contact: ContactTrickEntry
+    @Binding var state: ContactTrickState
+
+    private static let formatter: DateFormatter = {
+        let formatter = DateFormatter()
+        formatter.dateFormat = "HH:mm"
+        return formatter
+    }()
+
+    static func getImage(
+        contact: ContactTrickEntry,
+        state: ContactTrickState
+    ) -> UIImage {
+        let width = 1024.0
+        let height = 1024.0
+        var rect = CGRect(x: 0, y: 0, width: width, height: height)
+        let textColor: Color = contact.darkMode ?
+            Color(red: 250 / 256, green: 250 / 256, blue: 250 / 256) :
+            Color(red: 20 / 256, green: 20 / 256, blue: 20 / 256)
+        let secondaryTextColor: Color = contact.darkMode ?
+            Color(red: 220 / 256, green: 220 / 256, blue: 220 / 256) :
+            Color(red: 40 / 256, green: 40 / 256, blue: 40 / 256)
+        let fontWeight = contact.fontWeight.toUI()
+
+        UIGraphicsBeginImageContext(rect.size)
+        if let context = UIGraphicsGetCurrentContext() {
+            context.setShouldAntialias(true)
+            context.setAllowsAntialiasing(true)
+        }
+
+        let ringWidth = Double(contact.ringWidth) / 100.0
+        let ringGap = Double(contact.ringGap) / 100.0
+        let outerGap = 0.03
+
+        if contact.ring1 != .none {
+            rect = CGRect(
+                x: rect.minX + width * outerGap,
+                y: rect.minY + height * outerGap,
+                width: rect.width - width * outerGap * 2,
+                height: rect.height - height * outerGap * 2
+            )
+
+            let ringRect = CGRect(
+                x: rect.minX + width * ringWidth * 0.5,
+                y: rect.minY + height * ringWidth * 0.5,
+                width: rect.width - width * ringWidth,
+                height: rect.height - height * ringWidth
+            )
+
+            drawRing(ring: contact.ring1, contact: contact, state: state, rect: ringRect, strokeWidth: width * ringWidth)
+
+            rect = CGRect(
+                x: rect.minX + width * (ringWidth + ringGap),
+                y: rect.minY + height * (ringWidth + ringGap),
+                width: rect.width - width * (ringWidth + ringGap) * 2,
+                height: rect.height - height * (ringWidth + ringGap) * 2
+            )
+        }
+
+        switch contact.layout {
+        case .single:
+            let showTop = contact.top != .none
+            let showBottom = contact.bottom != .none
+
+            let centerX = rect.minX + rect.width / 2
+            let centerY = rect.minY + rect.height / 2
+            let radius = min(rect.width, rect.height) / 2
+
+            var primaryHeight = radius * 0.8
+            let topHeight = radius * 0.5
+            var bottomHeight = radius * 0.5
+
+            var primaryY = centerY - primaryHeight / 2
+
+            if contact.bottom == .none, contact.top != .none {
+                primaryY += radius * 0.2
+            }
+            if contact.bottom != .none, contact.top == .none {
+                primaryY -= radius * 0.2
+            }
+
+            let topY = primaryY - topHeight
+            var bottomY = primaryY + primaryHeight
+
+            let primaryWidth = 2 * sqrt(radius * radius - (primaryHeight * 0.5) * (primaryHeight * 0.5))
+            let topWidth = 2 *
+                sqrt(radius * radius - (topHeight + primaryHeight * 0.5) * (topHeight + primaryHeight * 0.5))
+            var bottomWidth = 2 *
+                sqrt(radius * radius - (bottomHeight + primaryHeight * 0.5) * (bottomHeight + primaryHeight * 0.5))
+
+            if contact.bottom != .none, contact.top == .none {
+                // move things around a little bit to give more space to the bottom area
+                if contact.ring1 == .iob || contact.ring1 == .cob || contact.ring1 == .iobcob ||
+                    (contact.bottom == .trend && contact.ring1 == .loop)
+                {
+                    bottomHeight = bottomHeight + height * ringWidth * 2
+                    bottomWidth = bottomWidth + width * ringWidth * 2
+                } else if contact.ring1 == .loop {
+                    primaryHeight = primaryHeight - height * ringWidth
+                    bottomY = primaryY + primaryHeight
+                    bottomHeight = bottomHeight + height * ringWidth * 2
+                    bottomWidth = bottomWidth + width * ringWidth * 2
+                }
+            }
+
+            let primaryRect = (showTop || showBottom) ? CGRect(
+                x: centerX - primaryWidth * 0.5,
+                y: primaryY,
+                width: primaryWidth,
+                height: primaryHeight
+            ) : rect
+            let topRect = CGRect(
+                x: centerX - topWidth * 0.5,
+                y: topY,
+                width: topWidth,
+                height: topHeight
+            )
+            let bottomRect = CGRect(
+                x: centerX - bottomWidth * 0.5,
+                y: bottomY,
+                width: bottomWidth,
+                height: bottomHeight
+            )
+            let secondaryFontSize = contact.secondaryFontSize
+
+            displayPiece(
+                value: contact.primary,
+                contact: contact,
+                state: state,
+                rect: primaryRect,
+                fitHeigh: false,
+                fontName: contact.fontName,
+                fontSize: contact.fontSize,
+                fontWeight: fontWeight,
+                fontTracking: contact.fontTracking,
+                color: textColor
+            )
+            if showTop {
+                displayPiece(
+                    value: contact.top,
+                    contact: contact,
+                    state: state,
+                    rect: topRect,
+                    fitHeigh: true,
+                    fontName: contact.fontName,
+                    fontSize: secondaryFontSize,
+                    fontWeight: fontWeight,
+                    fontTracking: contact.fontTracking,
+                    color: secondaryTextColor
+                )
+            }
+            if showBottom {
+                displayPiece(
+                    value: contact.bottom,
+                    contact: contact,
+                    state: state,
+                    rect: bottomRect,
+                    fitHeigh: true,
+                    fontName: contact.fontName,
+                    fontSize: secondaryFontSize,
+                    fontWeight: fontWeight,
+                    fontTracking: contact.fontTracking,
+                    color: secondaryTextColor
+                )
+            }
+
+        case .split:
+            let centerX = rect.origin.x + rect.size.width / 2
+            let centerY = rect.origin.y + rect.size.height / 2
+            let radius = min(rect.size.width, rect.size.height) / 2
+
+            let rectangleHeight = radius * sqrt(2) / 2
+            let rectangleWidth = sqrt(2) * radius
+
+            let topY = centerY - rectangleHeight
+            let bottomY = centerY
+
+            let topRect = CGRect(
+                x: centerX - rectangleWidth / 2,
+                y: topY,
+                width: rectangleWidth,
+                height: rectangleHeight
+            )
+            let bottomRect = CGRect(
+                x: centerX - rectangleWidth / 2,
+                y: bottomY,
+                width: rectangleWidth,
+                height: rectangleHeight
+            )
+            let topFontSize = contact.fontSize
+            let bottomFontSize = contact.secondaryFontSize
+
+            displayPiece(
+                value: contact.top,
+                contact: contact,
+                state: state,
+                rect: topRect,
+                fitHeigh: true,
+                fontName: contact.fontName,
+                fontSize: topFontSize,
+                fontWeight: fontWeight,
+                fontTracking: contact.fontTracking,
+                color: textColor
+            )
+            displayPiece(
+                value: contact.bottom,
+                contact: contact,
+                state: state,
+                rect: bottomRect,
+                fitHeigh: true,
+                fontName: contact.fontName,
+                fontSize: bottomFontSize,
+                fontWeight: fontWeight,
+                fontTracking: contact.fontTracking,
+                color: textColor
+            )
+        }
+        let image = UIGraphicsGetImageFromCurrentImageContext()
+        UIGraphicsEndImageContext()
+        return image ?? UIImage()
+    }
+
+    private static func displayPiece(
+        value: ContactTrickValue,
+        contact: ContactTrickEntry,
+        state: ContactTrickState,
+        rect: CGRect,
+        fitHeigh: Bool,
+        fontName: String?,
+        fontSize: Int,
+        fontWeight: UIFont.Weight,
+        fontTracking: FontTracking,
+        color: Color
+    ) {
+//        guard let context = UIGraphicsGetCurrentContext() else {
+//            return
+//        }
+//
+//        // Set the fill color (optional)
+//        context.setFillColor(UIColor.red.cgColor)
+//
+//        // Set the stroke color (optional)
+//        context.setStrokeColor(UIColor.black.cgColor)
+//
+//        // Set the line width (optional)
+//        context.setLineWidth(2.0)
+//
+//        // Create a rectangle
+//        let rectangle = CGRect(x: 50, y: 50, width: 100, height: 100)
+//
+//        // Fill the rectangle (if fill color is set)
+//        context.fill(rect)
+//
+//        // Stroke the rectangle (if stroke color is set)
+//        context.stroke(rect)
+
+        guard value != .none else { return }
+        if value == .ring {
+            drawRing(
+                ring: .loop,
+                contact: contact,
+                state: state,
+                rect: CGRect(
+                    x: rect.minX + rect.width * 0.10,
+                    y: rect.minY + rect.height * 0.10,
+                    width: rect.width * 0.80,
+                    height: rect.height * 0.80
+                ),
+                strokeWidth: 10.0
+            )
+            return
+        }
+        let text: String? = switch value {
+        case .glucose: state.glucose
+        case .eventualBG: state.eventualBG
+        case .delta: state.delta
+        case .trend: state.trend
+        case .lastLoopDate: state.lastLoopDate.map({ formatter.string(from: $0) })
+        case .cob: state.cobText
+        case .iob: state.iobText
+        default: nil
+        }
+
+        let textColor: Color = switch value {
+        case .cob: .loopYellow
+        default: color
+        }
+
+        if let text = text {
+            drawText(
+                text: text,
+                rect: rect,
+                fitHeigh: fitHeigh,
+                fontName: fontName,
+                fontSize: fontSize,
+                fontWeight: fontWeight,
+                fontTracking: fontTracking,
+                color: textColor
+            )
+        }
+    }
+
+    private static func drawText(
+        text: String,
+        rect: CGRect,
+        fitHeigh: Bool,
+        fontName: String?,
+        fontSize: Int,
+        fontWeight: UIFont.Weight,
+        fontTracking: FontTracking,
+        color: Color
+    ) {
+        var theFontSize = fontSize
+
+        func makeAttributes(_ size: Int) -> [NSAttributedString.Key: Any] {
+            let font = if let fontName {
+                UIFont(name: fontName, size: CGFloat(size)) ?? UIFont.systemFont(ofSize: CGFloat(size), weight: fontWeight)
+            } else {
+                UIFont.systemFont(ofSize: CGFloat(size), weight: fontWeight)
+            }
+            return [
+                .font: font,
+                .foregroundColor: UIColor(color),
+                .tracking: fontTracking.value * Double(fontSize)
+            ]
+        }
+
+        var attributes: [NSAttributedString.Key: Any] = makeAttributes(theFontSize)
+
+        var stringSize = text.size(withAttributes: attributes)
+        while stringSize.width > rect.width * 0.90 || fitHeigh && (stringSize.height > rect.height * 0.95), theFontSize > 50 {
+            theFontSize = theFontSize - 10
+            attributes = makeAttributes(theFontSize)
+            stringSize = text.size(withAttributes: attributes)
+        }
+
+        text.draw(
+            in: CGRectMake(
+                rect.minX + (rect.width - stringSize.width) / 2,
+                rect.minY + (rect.height - stringSize.height) / 2,
+                rect.minX + stringSize.width,
+                rect.minY + stringSize.height
+            ),
+            withAttributes: attributes
+        )
+    }
+
+    private static func drawRing(
+        ring: ContactTrickLargeRing,
+        contact: ContactTrickEntry,
+        state: ContactTrickState,
+        rect: CGRect,
+        strokeWidth: Double
+    ) {
+        guard let context = UIGraphicsGetCurrentContext() else {
+            return
+        }
+        switch ring {
+        case .loop:
+            let color = ringColor(contact: contact, state: state)
+
+            let strokeWidth = strokeWidth
+            let center = CGPoint(x: rect.midX, y: rect.midY)
+            let radius = min(rect.width, rect.height) / 2 - strokeWidth / 2
+
+            context.setLineWidth(strokeWidth)
+            context.setStrokeColor(UIColor(color).cgColor)
+
+            context.addArc(center: center, radius: radius, startAngle: 0, endAngle: 2 * .pi, clockwise: false)
+
+            context.strokePath()
+        case .iob:
+            if let iob = state.iob, state.maxIOB > 0.1 {
+                drawProgressBar(
+                    rect: rect,
+                    progress: Double(iob) / Double(state.maxIOB),
+                    colors: [contact.darkMode ? .blue : .blue, contact.darkMode ? .pink : .red],
+                    strokeWidth: strokeWidth
+                )
+            }
+        case .cob:
+            if let cob = state.cob, state.maxCOB > 0.01 {
+                drawProgressBar(
+                    rect: rect,
+                    progress: Double(cob) / Double(state.maxCOB),
+                    colors: [.loopYellow, .red],
+                    strokeWidth: strokeWidth
+                )
+            }
+        case .iobcob:
+            if state.maxIOB > 0.01, state.maxCOB > 0.01 {
+                drawDoubleProgressBar(
+                    rect: rect,
+                    progress1: state.iob.map { Double($0) / Double(state.maxIOB) },
+                    progress2: state.cob.map { Double($0) / Double(state.maxCOB) },
+                    colors1: [contact.darkMode ? .blue : .blue, contact.darkMode ? .pink : .red],
+                    colors2: [.loopYellow, .red],
+                    strokeWidth: strokeWidth
+                )
+            }
+        default:
+            break
+        }
+    }
+
+    private static func drawProgressBar(
+        rect: CGRect,
+        progress: Double,
+        colors: [Color],
+        strokeWidth: Double
+    ) {
+        let startAngle: CGFloat = -(.pi + .pi / 4.0)
+        let endAngle: CGFloat = .pi / 4.0
+
+        drawGradientArc(
+            rect: rect,
+            progress: progress,
+            colors: colors,
+            strokeWidth: strokeWidth,
+            startAngle: startAngle,
+            endAngle: endAngle,
+            gradientDirection: .leftToRight
+        )
+    }
+
+    private static func drawDoubleProgressBar(
+        rect: CGRect,
+        progress1: Double?,
+        progress2: Double?,
+        colors1: [Color],
+        colors2: [Color],
+        strokeWidth: Double
+    ) {
+        if let progress1 = progress1 {
+            let startAngle1: CGFloat = .pi / 2 + .pi / 5
+            let endAngle1: CGFloat = 3 * .pi / 2 - .pi / 5
+            drawGradientArc(
+                rect: rect,
+                progress: progress1,
+                colors: colors1,
+                strokeWidth: strokeWidth,
+                startAngle: startAngle1,
+                endAngle: endAngle1,
+                gradientDirection: .bottomToTop
+            )
+        }
+        if let progress2 = progress2 {
+            let startAngle2: CGFloat = .pi / 2 - .pi / 5
+            let endAngle2: CGFloat = -.pi / 2 + .pi / 5
+            drawGradientArc(
+                rect: rect,
+                progress: progress2,
+                colors: colors2,
+                strokeWidth: strokeWidth,
+                startAngle: startAngle2,
+                endAngle: endAngle2,
+                gradientDirection: .bottomToTop
+            )
+        }
+    }
+
+    private static func drawGradientArc(
+        rect: CGRect,
+        progress: Double,
+        colors: [Color],
+        strokeWidth: Double,
+        startAngle: Double,
+        endAngle: Double,
+        gradientDirection: GradientDirection
+    ) {
+        guard let context = UIGraphicsGetCurrentContext() else {
+            return
+        }
+
+        let colors = colors.map { c in UIColor(c).cgColor }
+        let locations: [CGFloat] = [0.0, 1.0]
+        guard let gradient = CGGradient(
+            colorsSpace: CGColorSpaceCreateDeviceRGB(),
+            colors: colors as CFArray,
+            locations: locations
+        ) else {
+            return
+        }
+
+        context.saveGState()
+
+        let center = CGPoint(x: rect.midX, y: rect.midY)
+        let radius = min(rect.width, rect.height) / 2 - strokeWidth / 2
+
+        // angle - The angle to the starting point of the arc, measured in radians from the positive x-axis.
+
+        context.setLineWidth(strokeWidth)
+        context.setLineCap(.round)
+
+        let circumference = 2 * .pi * radius
+        let offsetAngle = (strokeWidth / circumference * 1.1) * 2 * .pi
+
+        let (start, middle, end) = if startAngle > endAngle {
+            (
+                endAngle,
+                startAngle - (startAngle - endAngle) * max(min(progress, 1.0), 0.0),
+                startAngle
+            )
+        } else {
+            (
+                startAngle,
+                startAngle + (endAngle - startAngle) * max(min(progress, 1.0), 0.0),
+                endAngle
+            )
+        }
+
+        if start < middle - offsetAngle {
+            let arcPath1 = UIBezierPath()
+            arcPath1.addArc(
+                withCenter: center,
+                radius: radius,
+                startAngle: start,
+                endAngle: middle - offsetAngle,
+                clockwise: true
+            )
+            context.addPath(arcPath1.cgPath)
+        }
+
+        if middle + offsetAngle < end {
+            let arcPath2 = UIBezierPath()
+            arcPath2.addArc(
+                withCenter: center,
+                radius: radius,
+                startAngle: middle + offsetAngle,
+                endAngle: end,
+                clockwise: true
+            )
+            context.addPath(arcPath2.cgPath)
+        }
+
+        context.replacePathWithStrokedPath()
+        context.clip()
+
+        switch gradientDirection {
+        case .bottomToTop:
+            context.drawLinearGradient(
+                gradient,
+                start: CGPoint(x: rect.midX, y: rect.maxY),
+                end: CGPoint(x: rect.midX, y: rect.minY),
+                options: []
+            )
+
+        case .leftToRight:
+            context.drawLinearGradient(
+                gradient,
+                start: CGPoint(x: rect.minX, y: rect.midY),
+                end: CGPoint(x: rect.maxX, y: rect.midY),
+                options: []
+            )
+        }
+        context.resetClip()
+
+        let circleCenter = CGPoint(
+            x: center.x + radius * cos(middle),
+            y: center.y + radius * sin(middle)
+        )
+
+        context.setLineWidth(strokeWidth * 0.7)
+        context.setStrokeColor(UIColor.white.cgColor)
+        context.addArc(
+            center: circleCenter,
+            radius: 0,
+            startAngle: 0,
+            endAngle: .pi * 2,
+            clockwise: true
+        )
+        context.strokePath()
+
+        context.restoreGState()
+    }
+
+    private static func ringColor(
+        contact _: ContactTrickEntry,
+        state: ContactTrickState
+    ) -> Color {
+        guard let lastLoopDate = state.lastLoopDate else {
+            return .loopGray
+        }
+        let delta = Date().timeIntervalSince(lastLoopDate) - Config.lag
+
+        if delta <= 5.minutes.timeInterval {
+            return .loopGreen
+        } else if delta <= 10.minutes.timeInterval {
+            return .loopYellow
+        } else {
+            return .loopRed
+        }
+    }
+
+    var uiImage: UIImage {
+        ContactPicture.getImage(contact: contact, state: state)
+    }
+
+    var body: some View {
+        Image(uiImage: uiImage)
+            .frame(width: 256, height: 256)
+    }
+}
+
+extension FontWeight {
+    func toUI() -> UIFont.Weight {
+        switch self {
+        case .light:
+            UIFont.Weight.light
+        case .regular:
+            UIFont.Weight.regular
+        case .medium:
+            UIFont.Weight.medium
+        case .semibold:
+            UIFont.Weight.semibold
+        case .bold:
+            UIFont.Weight.bold
+        case .black:
+            UIFont.Weight.black
+        }
+    }
+}
+
+enum GradientDirection: Int {
+    case leftToRight
+    case bottomToTop
+}
+
+struct ContactPicturePreview: View {
+    @Binding var contact: ContactTrickEntry
+    @Binding var state: ContactTrickState
+
+    var body: some View {
+        ZStack {
+            ContactPicture(contact: $contact, state: $state)
+            Circle()
+                .stroke(lineWidth: 20)
+                .foregroundColor(.white)
+        }
+        .frame(width: 256, height: 256)
+        .clipShape(Circle())
+        .preferredColorScheme($contact.wrappedValue.darkMode ? .dark : .light)
+    }
+}
+
+struct ContactPicture_Previews: PreviewProvider {
+    struct Preview: View {
+        @State var rangeIndicator: Bool = true
+        @State var darkMode: Bool = true
+        @State var fontSize: Int = 130
+        @State var fontWeight: UIFont.Weight = .bold
+        @State var fontName: String? = "AmericanTypewriter"
+
+        var body: some View {
+            ContactPicturePreview(
+                contact: .constant(
+                    ContactTrickEntry(
+                        primary: .glucose,
+                        top: .delta,
+                        bottom: .trend,
+                        fontSize: 100,
+                        fontWeight: .medium
+                    )
+                ),
+                state: .constant(ContactTrickState(
+                    glucose: "6.8",
+                    trend: "↗︎",
+                    delta: "+0.2",
+                    cob: 25,
+                    cobText: "25"
+                ))
+            ).previewDisplayName("bg + trend + delta")
+
+            ContactPicturePreview(
+                contact: .constant(
+                    ContactTrickEntry(
+                        ring1: .iob,
+                        primary: .glucose,
+                        bottom: .trend,
+                        fontSize: 100,
+                        fontWeight: .medium
+                    )
+                ),
+                state: .constant(ContactTrickState(
+                    glucose: "6.8",
+                    trend: "↗︎",
+                    iob: 6.1,
+                    iobText: "6.1",
+                    maxIOB: 8.0
+                ))
+            ).previewDisplayName("bg + trend + iob ring")
+
+            ContactPicturePreview(
+                contact: .constant(
+                    ContactTrickEntry(
+                        primary: .glucose,
+                        top: .ring,
+                        bottom: .trend,
+                        fontSize: 100,
+                        fontWeight: .medium
+                    )
+                ),
+                state: .constant(ContactTrickState(
+                    glucose: "6.8",
+                    trend: "↗︎",
+                    lastLoopDate: .now
+                ))
+
+            ).previewDisplayName("bg + trend + ring")
+
+            ContactPicturePreview(
+                contact: .constant(
+                    ContactTrickEntry(
+                        ring1: .loop,
+                        primary: .glucose,
+                        top: .none,
+                        bottom: .trend,
+                        fontSize: 100,
+                        fontWeight: .medium
+                    )
+                ),
+                state: .constant(ContactTrickState(
+                    glucose: "8.8",
+                    trend: "→",
+                    lastLoopDate: .now
+                ))
+            ).previewDisplayName("bg + trend + ring1")
+
+            ContactPicturePreview(
+                contact: .constant(
+                    ContactTrickEntry(
+                        ring1: .loop,
+                        primary: .glucose,
+                        top: .none,
+                        bottom: .eventualBG,
+                        fontSize: 100,
+                        fontWeight: .medium
+                    )
+                ),
+                state: .constant(ContactTrickState(
+                    glucose: "6.8",
+                    lastLoopDate: .now - 7.minutes,
+                    eventualBG: "6.2"
+                ))
+            ).previewDisplayName("bg + eventual + ring1")
+
+            ContactPicturePreview(
+                contact: .constant(
+                    ContactTrickEntry(
+                        ring1: .loop,
+                        primary: .lastLoopDate,
+                        top: .none,
+                        bottom: .none,
+                        fontSize: 100,
+                        fontWeight: .medium
+                    )
+                ),
+                state: .constant(ContactTrickState(
+                    glucose: "6.8",
+                    trend: "↗︎",
+                    lastLoopDate: .now - 2.minutes
+                ))
+            ).previewDisplayName("lastLoopDate + ring1")
+
+            ContactPicturePreview(
+                contact: .constant(
+                    ContactTrickEntry(
+                        ring1: .loop,
+                        primary: .glucose,
+                        top: .none,
+                        bottom: .none,
+                        fontSize: 100,
+                        fontWeight: .medium
+                    )
+                ),
+                state: .constant(ContactTrickState(
+                    glucose: "6.8",
+                    lastLoopDate: .now,
+                    iob: 6.1,
+                    iobText: "6.1",
+                    maxIOB: 8.0
+                ))
+            ).previewDisplayName("bg + ring1 + ring2")
+
+            ContactPicturePreview(
+                contact: .constant(
+                    ContactTrickEntry(
+                        layout: .split,
+                        top: .iob,
+                        bottom: .cob,
+                        fontSize: 100,
+                        fontWeight: .medium
+                    )
+                ),
+                state: .constant(ContactTrickState(
+                    iob: 1.5,
+                    iobText: "1.5",
+                    cob: 25,
+                    cobText: "25"
+                ))
+            ).previewDisplayName("iob + cob")
+
+            ContactPicturePreview(
+                contact: .constant(
+                    ContactTrickEntry(
+                        layout: .single,
+                        ring1: .iobcob,
+                        primary: .none,
+                        ringWidth: 8,
+                        ringGap: 3,
+                        fontSize: 100,
+                        fontWeight: .medium
+                    )
+                ),
+                state: .constant(ContactTrickState(
+                    iob: 1,
+                    iobText: "5.5",
+                    cob: 25,
+                    cobText: "25",
+                    maxIOB: 10,
+                    maxCOB: 120
+                ))
+            ).previewDisplayName("iobcob ring")
+
+            ContactPicturePreview(
+                contact: .constant(
+                    ContactTrickEntry(
+                        layout: .single,
+                        ring1: .iobcob,
+                        primary: .none,
+                        fontSize: 100,
+                        fontWeight: .medium
+                    )
+                ),
+                state: .constant(ContactTrickState(
+                    iob: -0.2,
+                    iobText: "0.0",
+                    cob: 0,
+                    cobText: "0",
+                    maxIOB: 10,
+                    maxCOB: 120
+                ))
+            ).previewDisplayName("iobcob ring (0/0)")
+
+            ContactPicturePreview(
+                contact: .constant(
+                    ContactTrickEntry(
+                        layout: .single,
+                        ring1: .iobcob,
+                        primary: .none,
+                        fontSize: 100,
+                        fontWeight: .medium
+                    )
+                ),
+                state: .constant(ContactTrickState(
+                    iob: 10,
+                    iobText: "0.0",
+                    cob: 120,
+                    cobText: "0",
+                    maxIOB: 10,
+                    maxCOB: 120
+                ))
+            ).previewDisplayName("iobcob ring (max/max)")
+
+            ContactPicturePreview(
+                contact: .constant(
+                    ContactTrickEntry(
+                        layout: .single,
+                        ring1: .iobcob,
+                        primary: .glucose,
+                        bottom: .trend,
+                        fontSize: 100,
+                        fontWeight: .medium
+                    )
+                ),
+                state: .constant(ContactTrickState(
+                    glucose: "6.8",
+                    trend: "↗︎",
+                    iob: 5.5,
+                    iobText: "5.5",
+                    cob: 25,
+                    cobText: "25",
+                    maxIOB: 10,
+                    maxCOB: 120
+                ))
+            ).previewDisplayName("bg + trend + iobcob ring")
+        }
+    }
+
+    static var previews: some View {
+        Preview()
+    }
+}

+ 465 - 0
FreeAPS/Sources/Services/ContactTrick/ContactTrickManager.swift

@@ -0,0 +1,465 @@
+import Algorithms
+import Combine
+import Contacts
+import CoreData
+import Foundation
+import Swinject
+
+protocol ContactTrickManager {
+    func updateContacts(contacts: [ContactTrickEntry], completion: @escaping (Result<[ContactTrickEntry], Error>) -> Void)
+    var currentContacts: [ContactTrickEntry] { get }
+}
+
+final class BaseContactTrickManager: NSObject, ContactTrickManager, Injectable {
+    private var state = ContactTrickState()
+    private let processQueue = DispatchQueue(label: "BaseContactTrickManager.processQueue")
+
+    private let contactStore = CNContactStore()
+    private var workItem: DispatchWorkItem?
+
+    @Injected() private var broadcaster: Broadcaster!
+    @Injected() private var settingsManager: SettingsManager!
+    @Injected() private var apsManager: APSManager!
+    @Injected() private var storage: FileStorage!
+    @Injected() private var carbsStorage: CarbsStorage!
+    @Injected() private var tempTargetsStorage: TempTargetsStorage!
+    @Injected() private var glucoseStorage: GlucoseStorage!
+
+    private var glucoseFormatter: NumberFormatter {
+        let formatter = NumberFormatter()
+        formatter.numberStyle = .decimal
+        formatter.maximumFractionDigits = 0
+        if settingsManager.settings.units == .mmolL {
+            formatter.minimumFractionDigits = 1
+            formatter.maximumFractionDigits = 1
+        }
+        formatter.roundingMode = .halfUp
+        return formatter
+    }
+
+    private var eventualFormatter: NumberFormatter {
+        let formatter = NumberFormatter()
+        formatter.numberStyle = .decimal
+        formatter.maximumFractionDigits = 1
+        return formatter
+    }
+
+    private var deltaFormatter: NumberFormatter {
+        let formatter = NumberFormatter()
+        formatter.numberStyle = .decimal
+        formatter.maximumFractionDigits = settingsManager.settings.units == .mmolL ? 1 : 0
+        formatter.positivePrefix = "+"
+        formatter.negativePrefix = "-"
+        return formatter
+    }
+
+    private var targetFormatter: NumberFormatter {
+        let formatter = NumberFormatter()
+        formatter.numberStyle = .decimal
+        formatter.maximumFractionDigits = 1
+        return formatter
+    }
+
+    let context = CoreDataStack.shared.newTaskContext()
+    let viewContext = CoreDataStack.shared.persistentContainer.viewContext
+
+    private var coreDataPublisher: AnyPublisher<Set<NSManagedObject>, Never>?
+    private var subscriptions = Set<AnyCancellable>()
+
+    private var lifetime = Lifetime()
+
+    init(resolver: Resolver) {
+        super.init()
+        injectServices(resolver)
+        registerHandlers()
+        registerSubscribers()
+
+        coreDataPublisher =
+            changedObjectsOnManagedObjectContextDidSavePublisher()
+                .receive(on: DispatchQueue.global(qos: .background))
+                .share()
+                .eraseToAnyPublisher()
+
+        contacts = storage.retrieve(OpenAPS.Settings.contactTrick, as: [ContactTrickEntry].self)
+            ?? [ContactTrickEntry](from: OpenAPS.defaults(for: OpenAPS.Settings.contactTrick))
+            ?? []
+
+        knownIds = contacts.compactMap(\.contactId)
+
+        Task {
+            await configureState()
+        }
+
+        broadcaster.register(SettingsObserver.self, observer: self)
+        broadcaster.register(CarbsObserver.self, observer: self)
+
+    }
+
+    private func registerSubscribers() {
+        glucoseStorage.updatePublisher
+            .receive(on: DispatchQueue.global(qos: .background))
+            .sink { [weak self] _ in
+                guard let self = self else { return }
+                Task {
+                    await self.configureState()
+                }
+            }
+            .store(in: &subscriptions)
+    }
+
+    private func registerHandlers() {
+        coreDataPublisher?.filterByEntityName("OrefDetermination").sink { [weak self] _ in
+            guard let self = self else { return }
+            Task {
+                await self.configureState()
+            }
+        }.store(in: &subscriptions)
+
+        coreDataPublisher?.filterByEntityName("OverrideStored").sink { [weak self] _ in
+            guard let self = self else { return }
+            Task {
+                await self.configureState()
+            }
+        }.store(in: &subscriptions)
+
+        // Observes Deletion of Glucose Objects
+        coreDataPublisher?.filterByEntityName("GlucoseStored").sink { [weak self] _ in
+            guard let self = self else { return }
+            Task {
+                await self.configureState()
+            }
+        }.store(in: &subscriptions)
+    }
+
+    private var knownIds: [String] = []
+    private var contacts: [ContactTrickEntry] = []
+
+    var currentContacts: [ContactTrickEntry] {
+        contacts
+    }
+
+    private func fetchlastDetermination() async -> [NSManagedObjectID] {
+        let results = await CoreDataStack.shared.fetchEntitiesAsync(
+            ofType: OrefDetermination.self,
+            onContext: context,
+            predicate: NSPredicate.enactedDetermination,
+            key: "timestamp",
+            ascending: false,
+            fetchLimit: 1
+        )
+
+        return await context.perform {
+            guard let fetchedResults = results as? [OrefDetermination] else { return [] }
+
+            return fetchedResults.map(\.objectID)
+        }
+    }
+
+    private func fetchLatestOverride() async -> NSManagedObjectID? {
+        let results = await CoreDataStack.shared.fetchEntitiesAsync(
+            ofType: OverrideStored.self,
+            onContext: context,
+            predicate: NSPredicate.predicateForOneDayAgo,
+            key: "date",
+            ascending: false,
+            fetchLimit: 1,
+            propertiesToFetch: ["enabled", "percentage", "objectID"]
+        )
+
+        return await context.perform {
+            guard let fetchedResults = results as? [[String: Any]] else { return nil }
+
+            return fetchedResults.compactMap { $0["objectID"] as? NSManagedObjectID }.first
+        }
+    }
+
+    private func fetchGlucose() async -> [NSManagedObjectID] {
+        let results = await CoreDataStack.shared.fetchEntitiesAsync(
+            ofType: GlucoseStored.self,
+            onContext: context,
+            predicate: NSPredicate.predicateFor120MinAgo,
+            key: "date",
+            ascending: false,
+            fetchLimit: 24,
+            batchSize: 12
+        )
+
+        return await context.perform {
+            guard let glucoseResults = results as? [GlucoseStored] else {
+                return []
+            }
+
+            return glucoseResults.map(\.objectID)
+        }
+    }
+
+    @MainActor private func configureState() async {
+        let glucoseValuesIds = await fetchGlucose()
+        async let getLatestDeterminationIds = fetchlastDetermination()
+        guard let lastDeterminationId = await getLatestDeterminationIds.first else {
+            debugPrint("\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to get last Determination")
+            return
+        }
+
+        do {
+            let glucoseValues: [GlucoseStored] = await CoreDataStack.shared
+                .getNSManagedObject(with: glucoseValuesIds, context: viewContext)
+            let lastDetermination = try viewContext.existingObject(with: lastDeterminationId) as? OrefDetermination
+
+            await MainActor.run { [weak self] in
+                guard let self = self else { return }
+
+                if let firstGlucoseValue = glucoseValues.first {
+                    let value = self.settingsManager.settings.units == .mgdL
+                        ? Decimal(firstGlucoseValue.glucose)
+                        : Decimal(firstGlucoseValue.glucose).asMmolL
+
+                    self.state.glucose = self.glucoseFormatter.string(from: value as NSNumber)
+                    self.state.trend = firstGlucoseValue.directionEnum?.symbol
+
+                    let delta = glucoseValues.count >= 2
+                        ? Decimal(firstGlucoseValue.glucose) - Decimal(glucoseValues.dropFirst().first?.glucose ?? 0)
+                        : 0
+                    let deltaConverted = self.settingsManager.settings.units == .mgdL ? delta : delta.asMmolL
+                    self.state.delta = self.deltaFormatter.string(from: deltaConverted as NSNumber)
+                }
+
+                self.state.lastLoopDate = lastDetermination?.timestamp
+                self.state.maxCOB = self.settingsManager.preferences.maxCOB
+
+                self.state.iob = lastDetermination?.iob as? Decimal
+                if let cobValue = lastDetermination?.cob {
+                    self.state.cob = Decimal(cobValue)
+                } else {
+                    self.state.cob = 0
+                }
+
+                if let eventualBG = self.settingsManager.settings.units == .mgdL ? lastDetermination?
+                    .eventualBG : lastDetermination?
+                    .eventualBG?.decimalValue.asMmolL as NSDecimalNumber?
+                {
+                    let eventualBGAsString = self.eventualFormatter.string(from: eventualBG)
+                    self.state.eventualBG = eventualBGAsString.map { "⇢ " + $0 }
+                }
+
+                self.sendState()
+            }
+
+        } catch let error as NSError {
+            debugPrint("\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to configure state with error: \(error)")
+        }
+    }
+
+    private func sendState() {
+        guard let data = try? JSONEncoder().encode(state) else {
+            warning(.service, "Cannot encode watch state")
+            return
+        }
+
+        if contacts.isNotEmpty, CNContactStore.authorizationStatus(for: .contacts) == .authorized {
+            let newContacts = contacts.enumerated().map { index, entry in renderContact(entry, index + 1, self.state) }
+            if newContacts != contacts {
+                // when we create new contacts we store the IDs, in that case we need to write into the settings storage
+                storage.save(newContacts, as: OpenAPS.Settings.contactTrick)
+            }
+            contacts = newContacts
+        }
+    }
+
+    func updateContacts(contacts: [ContactTrickEntry], completion: @escaping (Result<[ContactTrickEntry], Error>) -> Void) {
+        self.contacts = contacts
+        let newIds = contacts.compactMap(\.contactId)
+
+        let knownSet = Set(knownIds)
+        let newSet = Set(newIds)
+        let removedIds = knownSet.subtracting(newSet)
+
+        processQueue.async {
+            removedIds.forEach { contactId in
+                if !self.deleteContact(contactId) {
+                    print("contacts cleanup, failed to delete contact \(contactId)")
+                }
+            }
+            self.sendState()
+            self.knownIds = self.contacts.compactMap(\.contactId)
+            completion(.success(self.contacts))
+        }
+    }
+
+    private let keysToFetch = [
+        CNContactImageDataKey,
+        CNContactGivenNameKey,
+        CNContactOrganizationNameKey
+    ] as [CNKeyDescriptor]
+
+    private func renderContact(_ _entry: ContactTrickEntry, _ index: Int, _ state: ContactTrickState) -> ContactTrickEntry {
+        var entry = _entry
+        let mutableContact: CNMutableContact
+        let saveRequest = CNSaveRequest()
+
+        if let contactId = entry.contactId {
+            do {
+                let contact = try contactStore.unifiedContact(withIdentifier: contactId, keysToFetch: keysToFetch)
+
+                mutableContact = contact.mutableCopy() as! CNMutableContact
+                updateContactFields(entry: entry, index: index, state: state, mutableContact: mutableContact)
+                saveRequest.update(mutableContact)
+            } catch let error as NSError {
+                if error.code == 200 { // 200: Updated Record Does Not Exist
+                    print("in handleEnabledContact, failed to fetch the contact, code 200, contact does not exist")
+                    mutableContact = createNewContact(
+                        entry: entry,
+                        index: index,
+                        state: state,
+                        saveRequest: saveRequest
+                    )
+                } else {
+                    print("in handleEnabledContact, failed to fetch the contact - \(getContactsErrorDetails(error))")
+                    return entry
+                }
+            } catch {
+                print("in handleEnabledContact, failed to fetch the contact: \(error.localizedDescription)")
+                return entry
+            }
+
+        } else {
+            print("no contact \(index) - creating")
+            mutableContact = createNewContact(
+                entry: entry,
+                index: index,
+                state: state,
+                saveRequest: saveRequest
+            )
+        }
+
+        saveUpdatedContact(saveRequest)
+
+        entry.contactId = mutableContact.identifier
+
+        return entry
+    }
+
+    private func createNewContact(
+        entry: ContactTrickEntry,
+        index: Int,
+        state: ContactTrickState,
+        saveRequest: CNSaveRequest
+    ) -> CNMutableContact {
+        let mutableContact = CNMutableContact()
+        updateContactFields(
+            entry: entry, index: index, state: state, mutableContact: mutableContact
+        )
+        print("creating a new contact, \(mutableContact.identifier)")
+        saveRequest.add(mutableContact, toContainerWithIdentifier: nil)
+        return mutableContact
+    }
+
+    private func updateContactFields(
+        entry: ContactTrickEntry,
+        index: Int,
+        state: ContactTrickState,
+        mutableContact: CNMutableContact
+    ) {
+        mutableContact.givenName = "Trio \(index)"
+        mutableContact
+            .organizationName =
+            "Created and managed by Trio - \(Date().formatted(date: .abbreviated, time: .shortened))"
+
+        mutableContact.imageData = ContactPicture.getImage(
+            contact: entry,
+            state: state
+        ).pngData()
+    }
+
+    private func deleteContact(_ contactId: String) -> Bool {
+        do {
+            print("deleting contact \(contactId)")
+            let keysToFetch = [CNContactIdentifierKey as CNKeyDescriptor] // we don't really need any, so just ID
+            let contact = try contactStore.unifiedContact(withIdentifier: contactId, keysToFetch: keysToFetch)
+
+            guard let mutableContact = contact.mutableCopy() as? CNMutableContact else {
+                print("in deleteContact, failed to get a mutable copy of the contact")
+                return false
+            }
+
+            let saveRequest = CNSaveRequest()
+            saveRequest.delete(mutableContact)
+            try contactStore.execute(saveRequest)
+            return true
+        } catch let error as NSError {
+            if error.code == 200 { // Updated Record Does Not Exist
+                return true
+            } else {
+                print("in deleteContact, failed to update the contact - \(getContactsErrorDetails(error))")
+                return false
+            }
+        } catch {
+            print("in deleteContact, failed to update the contact: \(error.localizedDescription)")
+            return false
+        }
+    }
+
+    private func saveUpdatedContact(_ saveRequest: CNSaveRequest) {
+        do {
+            try contactStore.execute(saveRequest)
+        } catch let error as NSError {
+            print("in updateContact, failed to update the contact - \(getContactsErrorDetails(error))")
+        } catch {
+            print("in updateContact, failed to update the contact: \(error.localizedDescription)")
+        }
+    }
+
+    private func getContactsErrorDetails(_ error: NSError) -> String {
+        var details: String?
+        if error.domain == CNErrorDomain {
+            switch error.code {
+            case CNError.authorizationDenied.rawValue:
+                details = "Authorization denied"
+            case CNError.communicationError.rawValue:
+                details = "Communication error"
+            case CNError.insertedRecordAlreadyExists.rawValue:
+                details = "Record already exists"
+            case CNError.dataAccessError.rawValue:
+                details = "Data access error"
+            default:
+                details = "Code \(error.code)"
+            }
+        }
+        return "\(details ?? "no details"): \(error.localizedDescription)"
+    }
+
+    private func descriptionForTarget(_ target: TempTarget) -> String {
+        let units = settingsManager.settings.units
+
+        var low = target.targetBottom
+        var high = target.targetTop
+        if units == .mmolL {
+            low = low?.asMmolL
+            high = high?.asMmolL
+        }
+
+        let description =
+            "\(targetFormatter.string(from: (low ?? 0) as NSNumber)!) - \(targetFormatter.string(from: (high ?? 0) as NSNumber)!)" +
+            " for \(targetFormatter.string(from: target.duration as NSNumber)!) min"
+
+        return description
+    }
+
+}
+
+extension BaseContactTrickManager:
+    CarbsObserver,
+    SettingsObserver
+{
+    func carbsDidUpdate(_: [CarbsEntry]) {
+        Task {
+            await configureState()
+        }
+    }
+
+    func settingsDidChange(_: FreeAPSSettings) {
+        Task {
+            await configureState()
+        }
+    }
+}

+ 15 - 0
FreeAPS/Sources/Services/ContactTrick/ContactTrickState.swift

@@ -0,0 +1,15 @@
+import Foundation
+
+struct ContactTrickState: Codable {
+    var glucose: String?
+    var trend: String?
+    var delta: String?
+    var lastLoopDate: Date?
+    var iob: Decimal?
+    var iobText: String?
+    var cob: Decimal?
+    var cobText: String?
+    var eventualBG: String?
+    var maxIOB: Decimal = 10.0
+    var maxCOB: Decimal = 120.0
+}