Преглед изворни кода

Merge branch 'dev' into feature/fix-ios-app-notification-duplicates-and-add-snooze-options

Deniz Cengiz пре 3 месеци
родитељ
комит
3d24f24df5
43 измењених фајлова са 1797 додато и 643 уклоњено
  1. 6 3
      .github/workflows/unit_tests.yml
  2. 1 1
      Config.xcconfig
  3. 1 1
      Gemfile
  4. 27 39
      Gemfile.lock
  5. 0 18
      Trio Watch App Extension/Assets.xcassets/Colors/Background_DarkBlue.colorset/Contents.json
  6. 0 18
      Trio Watch App Extension/Assets.xcassets/Colors/Background_DarkerDarkBlue.colorset/Contents.json
  7. 0 18
      Trio Watch App Extension/Assets.xcassets/Colors/Basal.colorset/Contents.json
  8. 0 18
      Trio Watch App Extension/Assets.xcassets/Colors/Insulin.colorset/Contents.json
  9. 0 18
      Trio Watch App Extension/Assets.xcassets/Colors/LoopGray.colorset/Contents.json
  10. 0 18
      Trio Watch App Extension/Assets.xcassets/Colors/LoopGreen.colorset/Contents.json
  11. 0 38
      Trio Watch App Extension/Assets.xcassets/Colors/LoopPink.colorset/Contents.json
  12. 0 18
      Trio Watch App Extension/Assets.xcassets/Colors/LoopRed.colorset/Contents.json
  13. 0 18
      Trio Watch App Extension/Assets.xcassets/Colors/TabBar.colorset/Contents.json
  14. 3 21
      Trio Watch App Extension/Assets.xcassets/Colors/UAM.colorset/Contents.json
  15. 0 18
      Trio Watch App Extension/Assets.xcassets/Colors/ZT.colorset/Contents.json
  16. 36 0
      Trio.xcodeproj/project.pbxproj
  17. 0 18
      Trio/Resources/Assets.xcassets/Colors/Background_DarkBlue.colorset/Contents.json
  18. 0 18
      Trio/Resources/Assets.xcassets/Colors/Background_DarkerDarkBlue.colorset/Contents.json
  19. 0 18
      Trio/Resources/Assets.xcassets/Colors/Basal.colorset/Contents.json
  20. 0 18
      Trio/Resources/Assets.xcassets/Colors/DarkerBlue.colorset/Contents.json
  21. 0 18
      Trio/Resources/Assets.xcassets/Colors/Insulin.colorset/Contents.json
  22. 0 38
      Trio/Resources/Assets.xcassets/Colors/Lemon.colorset/Contents.json
  23. 0 18
      Trio/Resources/Assets.xcassets/Colors/LoopGray.colorset/Contents.json
  24. 0 18
      Trio/Resources/Assets.xcassets/Colors/LoopGreen.colorset/Contents.json
  25. 0 38
      Trio/Resources/Assets.xcassets/Colors/LoopPink.colorset/Contents.json
  26. 0 18
      Trio/Resources/Assets.xcassets/Colors/LoopRed.colorset/Contents.json
  27. 0 18
      Trio/Resources/Assets.xcassets/Colors/ManualTempBasal.colorset/Contents.json
  28. 0 18
      Trio/Resources/Assets.xcassets/Colors/TabBar.colorset/Contents.json
  29. 3 21
      Trio/Resources/Assets.xcassets/Colors/UAM.colorset/Contents.json
  30. 0 18
      Trio/Resources/Assets.xcassets/Colors/ZT.colorset/Contents.json
  31. 0 18
      Trio/Resources/Assets.xcassets/Colors/minus.colorset/Contents.json
  32. 0 2
      Trio/Sources/Helpers/Color+Extensions.swift
  33. 15 0
      Trio/Sources/Helpers/String+Extensions.swift
  34. 78 0
      Trio/Sources/Localizations/Main/Localizable.xcstrings
  35. 21 0
      Trio/Sources/Models/ExportSetting.swift
  36. 4 0
      Trio/Sources/Modules/Settings/SettingsStateModel.swift
  37. 11 63
      Trio/Sources/Modules/Settings/View/SettingsRootView.swift
  38. 5 0
      Trio/Sources/Modules/SettingsExport/SettingsExportDataFlow.swift
  39. 3 0
      Trio/Sources/Modules/SettingsExport/SettingsExportProvider.swift
  40. 1329 0
      Trio/Sources/Modules/SettingsExport/SettingsExportStateModel.swift
  41. 192 0
      Trio/Sources/Modules/SettingsExport/View/SettingsExportRootView.swift
  42. 3 0
      Trio/Sources/Router/Screen.swift
  43. 59 0
      TrioTests/SettingsExportTests.swift

+ 6 - 3
.github/workflows/unit_tests.yml

@@ -28,7 +28,7 @@ jobs:
 
     steps:
       - name: Select Xcode version
-        run: sudo xcode-select -s /Applications/Xcode_16.3.app/Contents/Developer
+        run: sudo xcode-select -s /Applications/Xcode_16.4.app/Contents/Developer
 
       - name: Checkout code
         uses: actions/checkout@v4
@@ -55,13 +55,16 @@ jobs:
           echo "📂 Contents of .build:"
           ls -lah .build || echo ".build directory not found"
 
+      - name: List available simulators
+        run: xcrun simctl list devices available
+
       - name: Build for testing
         run: |
           set -o pipefail && \
           time xcodebuild build-for-testing \
             -workspace Trio.xcworkspace \
             -scheme "Trio Tests" \
-            -destination 'platform=iOS Simulator,name=iPhone 16,OS=18.4' \
+            -destination 'platform=iOS Simulator,name=iPhone 16,OS=18.5' \
 
       - name: Check for uncommitted changes
         run: |
@@ -104,7 +107,7 @@ jobs:
           time xcodebuild test-without-building \
             -workspace Trio.xcworkspace \
             -scheme "Trio Tests" \
-            -destination 'platform=iOS Simulator,name=iPhone 16,OS=18.4' \
+            -destination 'platform=iOS Simulator,name=iPhone 16,OS=18.5' \
             $([ "$ENABLE_PARALLEL_TESTING" = "true" ] && echo "-parallel-testing-enabled YES") \
             2>&1 | tee xcodebuild.log
 

+ 1 - 1
Config.xcconfig

@@ -19,7 +19,7 @@ TRIO_APP_GROUP_ID = group.org.nightscout.$(DEVELOPMENT_TEAM).trio.trio-app-group
 
 // The developers set the version numbers, please leave them alone
 APP_VERSION = 0.6.0
-APP_DEV_VERSION = 0.6.0.42
+APP_DEV_VERSION = 0.6.0.46
 APP_BUILD_NUMBER = 1
 COPYRIGHT_NOTICE =
 

+ 1 - 1
Gemfile

@@ -1,3 +1,3 @@
 source "https://rubygems.org"
 
-gem "fastlane", "2.230.0"
+gem "fastlane", "2.231.0"

+ 27 - 39
Gemfile.lock

@@ -1,18 +1,15 @@
 GEM
   remote: https://rubygems.org/
   specs:
-    CFPropertyList (3.0.7)
-      base64
-      nkf
-      rexml
+    CFPropertyList (3.0.8)
     abbrev (0.1.2)
-    addressable (2.8.7)
-      public_suffix (>= 2.0.2, < 7.0)
+    addressable (2.8.8)
+      public_suffix (>= 2.0.2, < 8.0)
     artifactory (3.0.17)
     atomos (0.1.3)
     aws-eventstream (1.4.0)
-    aws-partitions (1.1163.0)
-    aws-sdk-core (3.232.0)
+    aws-partitions (1.1206.0)
+    aws-sdk-core (3.241.4)
       aws-eventstream (~> 1, >= 1.3.0)
       aws-partitions (~> 1, >= 1.992.0)
       aws-sigv4 (~> 1.9)
@@ -20,18 +17,18 @@ GEM
       bigdecimal
       jmespath (~> 1, >= 1.6.1)
       logger
-    aws-sdk-kms (1.112.0)
-      aws-sdk-core (~> 3, >= 3.231.0)
+    aws-sdk-kms (1.121.0)
+      aws-sdk-core (~> 3, >= 3.241.4)
       aws-sigv4 (~> 1.5)
-    aws-sdk-s3 (1.199.0)
-      aws-sdk-core (~> 3, >= 3.231.0)
+    aws-sdk-s3 (1.211.0)
+      aws-sdk-core (~> 3, >= 3.241.3)
       aws-sdk-kms (~> 1)
       aws-sigv4 (~> 1.5)
     aws-sigv4 (1.12.1)
       aws-eventstream (~> 1, >= 1.0.2)
     babosa (1.0.4)
     base64 (0.2.0)
-    bigdecimal (3.2.3)
+    bigdecimal (4.0.1)
     claide (1.1.0)
     colored (1.2)
     colored2 (3.1.2)
@@ -45,36 +42,32 @@ GEM
     dotenv (2.8.1)
     emoji_regex (3.2.3)
     excon (0.112.0)
-    faraday (1.10.4)
+    faraday (1.8.0)
       faraday-em_http (~> 1.0)
       faraday-em_synchrony (~> 1.0)
       faraday-excon (~> 1.1)
-      faraday-httpclient (~> 1.0)
-      faraday-multipart (~> 1.0)
+      faraday-httpclient (~> 1.0.1)
       faraday-net_http (~> 1.0)
-      faraday-net_http_persistent (~> 1.0)
+      faraday-net_http_persistent (~> 1.1)
       faraday-patron (~> 1.0)
       faraday-rack (~> 1.0)
-      faraday-retry (~> 1.0)
+      multipart-post (>= 1.2, < 3)
       ruby2_keywords (>= 0.0.4)
-    faraday-cookie_jar (0.0.7)
+    faraday-cookie_jar (0.0.8)
       faraday (>= 0.8.0)
-      http-cookie (~> 1.0.0)
+      http-cookie (>= 1.0.0)
     faraday-em_http (1.0.0)
     faraday-em_synchrony (1.0.1)
     faraday-excon (1.1.0)
     faraday-httpclient (1.0.1)
-    faraday-multipart (1.1.1)
-      multipart-post (~> 2.0)
     faraday-net_http (1.0.2)
     faraday-net_http_persistent (1.2.0)
     faraday-patron (1.0.0)
     faraday-rack (1.0.0)
-    faraday-retry (1.0.3)
     faraday_middleware (1.2.1)
       faraday (~> 1.0)
     fastimage (2.4.0)
-    fastlane (2.230.0)
+    fastlane (2.231.0)
       CFPropertyList (>= 2.3, < 4.0.0)
       abbrev (~> 0.1.2)
       addressable (>= 2.8, < 3.0.0)
@@ -82,7 +75,7 @@ GEM
       aws-sdk-s3 (~> 1.0)
       babosa (>= 1.0.3, < 2.0.0)
       base64 (~> 0.2.0)
-      bundler (>= 1.12.0, < 3.0.0)
+      bundler (>= 1.17.3, < 5.0.0)
       colored (~> 1.2)
       commander (~> 4.6)
       csv (~> 3.3)
@@ -167,23 +160,23 @@ GEM
     httpclient (2.9.0)
       mutex_m
     jmespath (1.6.2)
-    json (2.15.0)
+    json (2.18.0)
     jwt (2.10.2)
       base64
     logger (1.7.0)
     mini_magick (4.13.2)
     mini_mime (1.1.5)
-    multi_json (1.17.0)
+    multi_json (1.19.1)
     multipart-post (2.4.1)
     mutex_m (0.3.0)
     nanaimo (0.4.0)
     naturally (2.3.0)
     nkf (0.2.0)
-    optparse (0.6.0)
+    optparse (0.8.1)
     os (1.1.4)
     plist (3.7.2)
-    public_suffix (6.0.2)
-    rake (13.3.0)
+    public_suffix (7.0.2)
+    rake (13.3.1)
     representable (3.2.0)
       declarative (< 0.1.0)
       trailblazer-option (>= 0.1.1, < 0.2.0)
@@ -227,16 +220,11 @@ GEM
       xcpretty (~> 0.2, >= 0.0.7)
 
 PLATFORMS
-  arm64-darwin-21
-  arm64-darwin-22
-  arm64-darwin-23
-  arm64-darwin-24
-  x86_64-darwin-19
-  x86_64-darwin-24
-  x86_64-linux
+  arm64-darwin-25
+  ruby
 
 DEPENDENCIES
-  fastlane (= 2.230.0)
+  fastlane (= 2.231.0)
 
 BUNDLED WITH
-   2.6.2
+  4.0.4

+ 0 - 18
Trio Watch App Extension/Assets.xcassets/Colors/Background_DarkBlue.colorset/Contents.json

@@ -11,24 +11,6 @@
         }
       },
       "idiom" : "universal"
-    },
-    {
-      "appearances" : [
-        {
-          "appearance" : "luminosity",
-          "value" : "dark"
-        }
-      ],
-      "color" : {
-        "color-space" : "srgb",
-        "components" : {
-          "alpha" : "1.000",
-          "blue" : "0.216",
-          "green" : "0.133",
-          "red" : "0.039"
-        }
-      },
-      "idiom" : "universal"
     }
   ],
   "info" : {

+ 0 - 18
Trio Watch App Extension/Assets.xcassets/Colors/Background_DarkerDarkBlue.colorset/Contents.json

@@ -11,24 +11,6 @@
         }
       },
       "idiom" : "universal"
-    },
-    {
-      "appearances" : [
-        {
-          "appearance" : "luminosity",
-          "value" : "dark"
-        }
-      ],
-      "color" : {
-        "color-space" : "srgb",
-        "components" : {
-          "alpha" : "1.000",
-          "blue" : "0.109",
-          "green" : "0.058",
-          "red" : "0.011"
-        }
-      },
-      "idiom" : "universal"
     }
   ],
   "info" : {

+ 0 - 18
Trio Watch App Extension/Assets.xcassets/Colors/Basal.colorset/Contents.json

@@ -11,24 +11,6 @@
         }
       },
       "idiom" : "universal"
-    },
-    {
-      "appearances" : [
-        {
-          "appearance" : "luminosity",
-          "value" : "dark"
-        }
-      ],
-      "color" : {
-        "color-space" : "srgb",
-        "components" : {
-          "alpha" : "0.500",
-          "blue" : "0.988",
-          "green" : "0.588",
-          "red" : "0.118"
-        }
-      },
-      "idiom" : "universal"
     }
   ],
   "info" : {

+ 0 - 18
Trio Watch App Extension/Assets.xcassets/Colors/Insulin.colorset/Contents.json

@@ -11,24 +11,6 @@
         }
       },
       "idiom" : "universal"
-    },
-    {
-      "appearances" : [
-        {
-          "appearance" : "luminosity",
-          "value" : "dark"
-        }
-      ],
-      "color" : {
-        "color-space" : "srgb",
-        "components" : {
-          "alpha" : "1.000",
-          "blue" : "0.988",
-          "green" : "0.588",
-          "red" : "0.118"
-        }
-      },
-      "idiom" : "universal"
     }
   ],
   "info" : {

+ 0 - 18
Trio Watch App Extension/Assets.xcassets/Colors/LoopGray.colorset/Contents.json

@@ -11,24 +11,6 @@
         }
       },
       "idiom" : "universal"
-    },
-    {
-      "appearances" : [
-        {
-          "appearance" : "luminosity",
-          "value" : "dark"
-        }
-      ],
-      "color" : {
-        "color-space" : "srgb",
-        "components" : {
-          "alpha" : "1.000",
-          "blue" : "0.741",
-          "green" : "0.741",
-          "red" : "0.741"
-        }
-      },
-      "idiom" : "universal"
     }
   ],
   "info" : {

+ 0 - 18
Trio Watch App Extension/Assets.xcassets/Colors/LoopGreen.colorset/Contents.json

@@ -11,24 +11,6 @@
         }
       },
       "idiom" : "universal"
-    },
-    {
-      "appearances" : [
-        {
-          "appearance" : "luminosity",
-          "value" : "dark"
-        }
-      ],
-      "color" : {
-        "color-space" : "srgb",
-        "components" : {
-          "alpha" : "1.000",
-          "blue" : "0.592",
-          "green" : "0.812",
-          "red" : "0.435"
-        }
-      },
-      "idiom" : "universal"
     }
   ],
   "info" : {

+ 0 - 38
Trio Watch App Extension/Assets.xcassets/Colors/LoopPink.colorset/Contents.json

@@ -1,38 +0,0 @@
-{
-  "colors" : [
-    {
-      "color" : {
-        "color-space" : "srgb",
-        "components" : {
-          "alpha" : "1.000",
-          "blue" : "0.796",
-          "green" : "0.750",
-          "red" : "1.000"
-        }
-      },
-      "idiom" : "universal"
-    },
-    {
-      "appearances" : [
-        {
-          "appearance" : "luminosity",
-          "value" : "dark"
-        }
-      ],
-      "color" : {
-        "color-space" : "srgb",
-        "components" : {
-          "alpha" : "1.000",
-          "blue" : "0.796",
-          "green" : "0.750",
-          "red" : "1.000"
-        }
-      },
-      "idiom" : "universal"
-    }
-  ],
-  "info" : {
-    "author" : "xcode",
-    "version" : 1
-  }
-}

+ 0 - 18
Trio Watch App Extension/Assets.xcassets/Colors/LoopRed.colorset/Contents.json

@@ -11,24 +11,6 @@
         }
       },
       "idiom" : "universal"
-    },
-    {
-      "appearances" : [
-        {
-          "appearance" : "luminosity",
-          "value" : "dark"
-        }
-      ],
-      "color" : {
-        "color-space" : "srgb",
-        "components" : {
-          "alpha" : "1.000",
-          "blue" : "0.341",
-          "green" : "0.341",
-          "red" : "0.922"
-        }
-      },
-      "idiom" : "universal"
     }
   ],
   "info" : {

+ 0 - 18
Trio Watch App Extension/Assets.xcassets/Colors/TabBar.colorset/Contents.json

@@ -11,24 +11,6 @@
         }
       },
       "idiom" : "universal"
-    },
-    {
-      "appearances" : [
-        {
-          "appearance" : "luminosity",
-          "value" : "dark"
-        }
-      ],
-      "color" : {
-        "color-space" : "srgb",
-        "components" : {
-          "alpha" : "1.000",
-          "blue" : "0.950",
-          "green" : "0.550",
-          "red" : "0.490"
-        }
-      },
-      "idiom" : "universal"
     }
   ],
   "info" : {

+ 3 - 21
Trio Watch App Extension/Assets.xcassets/Colors/UAM.colorset/Contents.json

@@ -5,27 +5,9 @@
         "color-space" : "srgb",
         "components" : {
           "alpha" : "1.000",
-          "blue" : "0.271",
-          "green" : "0.518",
-          "red" : "1.000"
-        }
-      },
-      "idiom" : "universal"
-    },
-    {
-      "appearances" : [
-        {
-          "appearance" : "luminosity",
-          "value" : "dark"
-        }
-      ],
-      "color" : {
-        "color-space" : "srgb",
-        "components" : {
-          "alpha" : "1.000",
-          "blue" : "0.271",
-          "green" : "0.518",
-          "red" : "1.000"
+          "blue" : "0.969",
+          "green" : "0.169",
+          "red" : "0.820"
         }
       },
       "idiom" : "universal"

+ 0 - 18
Trio Watch App Extension/Assets.xcassets/Colors/ZT.colorset/Contents.json

@@ -11,24 +11,6 @@
         }
       },
       "idiom" : "universal"
-    },
-    {
-      "appearances" : [
-        {
-          "appearance" : "luminosity",
-          "value" : "dark"
-        }
-      ],
-      "color" : {
-        "color-space" : "srgb",
-        "components" : {
-          "alpha" : "1.000",
-          "blue" : "0.937",
-          "green" : "0.380",
-          "red" : "0.443"
-        }
-      },
-      "idiom" : "universal"
     }
   ],
   "info" : {

+ 36 - 0
Trio.xcodeproj/project.pbxproj

@@ -456,6 +456,10 @@
 		C29E268A2DADFD2A00F87E75 /* GlucoseDailyPercentileChart.swift in Sources */ = {isa = PBXBuildFile; fileRef = C29E26892DADFD2A00F87E75 /* GlucoseDailyPercentileChart.swift */; };
 		C2A0A42F2CE03131003B98E8 /* ConstantValues.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2A0A42E2CE0312C003B98E8 /* ConstantValues.swift */; };
 		C2A6D1E42DB1581D0036DB66 /* GlucoseStatsSetup.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2A6D1E32DB1581D0036DB66 /* GlucoseStatsSetup.swift */; };
+		C2AA6CF62E1A734A00BF6C16 /* SettingsExportRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2AA6CF02E1A734A00BF6C16 /* SettingsExportRootView.swift */; };
+		C2AA6CF72E1A734A00BF6C16 /* SettingsExportProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2AA6CF32E1A734A00BF6C16 /* SettingsExportProvider.swift */; };
+		C2AA6CF82E1A734A00BF6C16 /* SettingsExportDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2AA6CF22E1A734A00BF6C16 /* SettingsExportDataFlow.swift */; };
+		C2AA6CF92E1A734A00BF6C16 /* SettingsExportStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2AA6CF42E1A734A00BF6C16 /* SettingsExportStateModel.swift */; };
 		C967DACD3B1E638F8B43BE06 /* ManualTempBasalStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CFCFE0781F9074C2917890E8 /* ManualTempBasalStateModel.swift */; };
 		CA370FC152BC98B3D1832968 /* BasalProfileEditorRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF8BCB0C37DEB5EC377B9612 /* BasalProfileEditorRootView.swift */; };
 		CC6C406E2ACDD69E009B8058 /* RawFetchedProfile.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC6C406D2ACDD69E009B8058 /* RawFetchedProfile.swift */; };
@@ -571,6 +575,7 @@
 		DD3A3CE92D29C97800AE478E /* Helper+ButtonStyles.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD3A3CE82D29C97800AE478E /* Helper+ButtonStyles.swift */; };
 		DD3C47B32DC5608A003DD20D /* newerSuggested.json in Resources */ = {isa = PBXBuildFile; fileRef = DD3C47B22DC5608A003DD20D /* newerSuggested.json */; };
 		DD3C47B52DC57E06003DD20D /* MainMigrationErrorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD3C47B42DC57E06003DD20D /* MainMigrationErrorView.swift */; };
+		DD3D60312F0377350021A33B /* ExportSetting.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD3D60302F0377350021A33B /* ExportSetting.swift */; };
 		DD3F1F832D9DC78800DCE7B3 /* UnitSelectionStepView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD3F1F822D9DC78300DCE7B3 /* UnitSelectionStepView.swift */; };
 		DD3F1F852D9DD84000DCE7B3 /* DeliveryLimitsStepView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD3F1F842D9DD83B00DCE7B3 /* DeliveryLimitsStepView.swift */; };
 		DD3F1F892D9E078D00DCE7B3 /* TherapySettingEditorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD3F1F882D9E078300DCE7B3 /* TherapySettingEditorView.swift */; };
@@ -1283,6 +1288,10 @@
 		C29E26892DADFD2A00F87E75 /* GlucoseDailyPercentileChart.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlucoseDailyPercentileChart.swift; sourceTree = "<group>"; };
 		C2A0A42E2CE0312C003B98E8 /* ConstantValues.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConstantValues.swift; sourceTree = "<group>"; };
 		C2A6D1E32DB1581D0036DB66 /* GlucoseStatsSetup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlucoseStatsSetup.swift; sourceTree = "<group>"; };
+		C2AA6CF02E1A734A00BF6C16 /* SettingsExportRootView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsExportRootView.swift; sourceTree = "<group>"; };
+		C2AA6CF22E1A734A00BF6C16 /* SettingsExportDataFlow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsExportDataFlow.swift; sourceTree = "<group>"; };
+		C2AA6CF32E1A734A00BF6C16 /* SettingsExportProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsExportProvider.swift; sourceTree = "<group>"; };
+		C2AA6CF42E1A734A00BF6C16 /* SettingsExportStateModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsExportStateModel.swift; sourceTree = "<group>"; };
 		C377490C77661D75E8C50649 /* ManualTempBasalRootView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ManualTempBasalRootView.swift; sourceTree = "<group>"; };
 		C8D1A7CA8C10C4403D4BBFA7 /* TreatmentsDataFlow.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = TreatmentsDataFlow.swift; sourceTree = "<group>"; };
 		CC6C406D2ACDD69E009B8058 /* RawFetchedProfile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RawFetchedProfile.swift; sourceTree = "<group>"; };
@@ -1403,6 +1412,7 @@
 		DD3A3CE82D29C97800AE478E /* Helper+ButtonStyles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Helper+ButtonStyles.swift"; sourceTree = "<group>"; };
 		DD3C47B22DC5608A003DD20D /* newerSuggested.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = newerSuggested.json; sourceTree = "<group>"; };
 		DD3C47B42DC57E06003DD20D /* MainMigrationErrorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainMigrationErrorView.swift; sourceTree = "<group>"; };
+		DD3D60302F0377350021A33B /* ExportSetting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExportSetting.swift; sourceTree = "<group>"; };
 		DD3F1F822D9DC78300DCE7B3 /* UnitSelectionStepView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnitSelectionStepView.swift; sourceTree = "<group>"; };
 		DD3F1F842D9DD83B00DCE7B3 /* DeliveryLimitsStepView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeliveryLimitsStepView.swift; sourceTree = "<group>"; };
 		DD3F1F882D9E078300DCE7B3 /* TherapySettingEditorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TherapySettingEditorView.swift; sourceTree = "<group>"; };
@@ -1898,6 +1908,7 @@
 				E592A3762CEEC038009A472C /* ContactImage */,
 				9E56E3626FAD933385101B76 /* History */,
 				195D80B22AF696EE00D25097 /* DynamicSettings */,
+				C2AA6CF52E1A734A00BF6C16 /* SettingsExport */,
 				DD17454C2C55CA0200211FAC /* GeneralSettings */,
 				F66B236E00924A05D6A9F9DF /* GlucoseNotificationSettings */,
 				F90692CD274B99850037068D /* HealthKit */,
@@ -2358,6 +2369,7 @@
 		388E5A5925B6F0250019842D /* Models */ = {
 			isa = PBXGroup;
 			children = (
+				DD3D60302F0377350021A33B /* ExportSetting.swift */,
 				DDFF204F2DB2C11900AB8A96 /* WatchStateSnapshot.swift */,
 				DDEBB05B2D89E9050032305D /* TimeInRangeType.swift */,
 				3B2F77852D7E52ED005ED9FA /* TDD.swift */,
@@ -3056,6 +3068,25 @@
 			path = "Trio Watch App";
 			sourceTree = "<group>";
 		};
+		C2AA6CF12E1A734A00BF6C16 /* View */ = {
+			isa = PBXGroup;
+			children = (
+				C2AA6CF02E1A734A00BF6C16 /* SettingsExportRootView.swift */,
+			);
+			path = View;
+			sourceTree = "<group>";
+		};
+		C2AA6CF52E1A734A00BF6C16 /* SettingsExport */ = {
+			isa = PBXGroup;
+			children = (
+				C2AA6CF12E1A734A00BF6C16 /* View */,
+				C2AA6CF22E1A734A00BF6C16 /* SettingsExportDataFlow.swift */,
+				C2AA6CF32E1A734A00BF6C16 /* SettingsExportProvider.swift */,
+				C2AA6CF42E1A734A00BF6C16 /* SettingsExportStateModel.swift */,
+			);
+			path = SettingsExport;
+			sourceTree = "<group>";
+		};
 		C2C98283C436DB934D7E7994 /* Treatments */ = {
 			isa = PBXGroup;
 			children = (
@@ -4404,6 +4435,10 @@
 				DD6A4E842DBEDD39008C4B26 /* AlgorithmSettingsImportantNotesStepView.swift in Sources */,
 				3811DEB025C9D88300A708ED /* BaseKeychain.swift in Sources */,
 				110AEDE42C5193D200615CC9 /* BolusIntentRequest.swift in Sources */,
+				C2AA6CF62E1A734A00BF6C16 /* SettingsExportRootView.swift in Sources */,
+				C2AA6CF72E1A734A00BF6C16 /* SettingsExportProvider.swift in Sources */,
+				C2AA6CF82E1A734A00BF6C16 /* SettingsExportDataFlow.swift in Sources */,
+				C2AA6CF92E1A734A00BF6C16 /* SettingsExportStateModel.swift in Sources */,
 				3811DE4325C9D4A100A708ED /* SettingsProvider.swift in Sources */,
 				45252C95D220E796FDB3B022 /* ConfigEditorDataFlow.swift in Sources */,
 				CE7CA34E2A064973004BE681 /* AppShortcuts.swift in Sources */,
@@ -4480,6 +4515,7 @@
 				CE7CA3512A064973004BE681 /* ApplyTempPresetIntent.swift in Sources */,
 				FA630397F76B582C8D8681A7 /* BasalProfileEditorProvider.swift in Sources */,
 				DD1745172C54389F00211FAC /* FeatureSettingsView.swift in Sources */,
+				DD3D60312F0377350021A33B /* ExportSetting.swift in Sources */,
 				DD9ECB712CA9A0BA00AA7C45 /* RemoteControlConfigProvider.swift in Sources */,
 				63E890B4D951EAA91C071D5C /* BasalProfileEditorStateModel.swift in Sources */,
 				38FEF3FA2737E42000574A46 /* BaseStateModel.swift in Sources */,

+ 0 - 18
Trio/Resources/Assets.xcassets/Colors/Background_DarkBlue.colorset/Contents.json

@@ -11,24 +11,6 @@
         }
       },
       "idiom" : "universal"
-    },
-    {
-      "appearances" : [
-        {
-          "appearance" : "luminosity",
-          "value" : "dark"
-        }
-      ],
-      "color" : {
-        "color-space" : "srgb",
-        "components" : {
-          "alpha" : "1.000",
-          "blue" : "0.216",
-          "green" : "0.133",
-          "red" : "0.039"
-        }
-      },
-      "idiom" : "universal"
     }
   ],
   "info" : {

+ 0 - 18
Trio/Resources/Assets.xcassets/Colors/Background_DarkerDarkBlue.colorset/Contents.json

@@ -11,24 +11,6 @@
         }
       },
       "idiom" : "universal"
-    },
-    {
-      "appearances" : [
-        {
-          "appearance" : "luminosity",
-          "value" : "dark"
-        }
-      ],
-      "color" : {
-        "color-space" : "srgb",
-        "components" : {
-          "alpha" : "1.000",
-          "blue" : "0.109",
-          "green" : "0.058",
-          "red" : "0.011"
-        }
-      },
-      "idiom" : "universal"
     }
   ],
   "info" : {

+ 0 - 18
Trio/Resources/Assets.xcassets/Colors/Basal.colorset/Contents.json

@@ -11,24 +11,6 @@
         }
       },
       "idiom" : "universal"
-    },
-    {
-      "appearances" : [
-        {
-          "appearance" : "luminosity",
-          "value" : "dark"
-        }
-      ],
-      "color" : {
-        "color-space" : "srgb",
-        "components" : {
-          "alpha" : "0.500",
-          "blue" : "0.988",
-          "green" : "0.588",
-          "red" : "0.118"
-        }
-      },
-      "idiom" : "universal"
     }
   ],
   "info" : {

+ 0 - 18
Trio/Resources/Assets.xcassets/Colors/DarkerBlue.colorset/Contents.json

@@ -11,24 +11,6 @@
         }
       },
       "idiom" : "universal"
-    },
-    {
-      "appearances" : [
-        {
-          "appearance" : "luminosity",
-          "value" : "dark"
-        }
-      ],
-      "color" : {
-        "color-space" : "srgb",
-        "components" : {
-          "alpha" : "1.000",
-          "blue" : "1.000",
-          "green" : "0.288",
-          "red" : "0.118"
-        }
-      },
-      "idiom" : "universal"
     }
   ],
   "info" : {

+ 0 - 18
Trio/Resources/Assets.xcassets/Colors/Insulin.colorset/Contents.json

@@ -11,24 +11,6 @@
         }
       },
       "idiom" : "universal"
-    },
-    {
-      "appearances" : [
-        {
-          "appearance" : "luminosity",
-          "value" : "dark"
-        }
-      ],
-      "color" : {
-        "color-space" : "srgb",
-        "components" : {
-          "alpha" : "1.000",
-          "blue" : "0.988",
-          "green" : "0.588",
-          "red" : "0.118"
-        }
-      },
-      "idiom" : "universal"
     }
   ],
   "info" : {

+ 0 - 38
Trio/Resources/Assets.xcassets/Colors/Lemon.colorset/Contents.json

@@ -1,38 +0,0 @@
-{
-  "colors" : [
-    {
-      "color" : {
-        "color-space" : "srgb",
-        "components" : {
-          "alpha" : "1.000",
-          "blue" : "0.089",
-          "green" : "0.940",
-          "red" : "1.000"
-        }
-      },
-      "idiom" : "universal"
-    },
-    {
-      "appearances" : [
-        {
-          "appearance" : "luminosity",
-          "value" : "dark"
-        }
-      ],
-      "color" : {
-        "color-space" : "srgb",
-        "components" : {
-          "alpha" : "1.000",
-          "blue" : "0.089",
-          "green" : "0.940",
-          "red" : "1.000"
-        }
-      },
-      "idiom" : "universal"
-    }
-  ],
-  "info" : {
-    "author" : "xcode",
-    "version" : 1
-  }
-}

+ 0 - 18
Trio/Resources/Assets.xcassets/Colors/LoopGray.colorset/Contents.json

@@ -11,24 +11,6 @@
         }
       },
       "idiom" : "universal"
-    },
-    {
-      "appearances" : [
-        {
-          "appearance" : "luminosity",
-          "value" : "dark"
-        }
-      ],
-      "color" : {
-        "color-space" : "srgb",
-        "components" : {
-          "alpha" : "1.000",
-          "blue" : "0.741",
-          "green" : "0.741",
-          "red" : "0.741"
-        }
-      },
-      "idiom" : "universal"
     }
   ],
   "info" : {

+ 0 - 18
Trio/Resources/Assets.xcassets/Colors/LoopGreen.colorset/Contents.json

@@ -11,24 +11,6 @@
         }
       },
       "idiom" : "universal"
-    },
-    {
-      "appearances" : [
-        {
-          "appearance" : "luminosity",
-          "value" : "dark"
-        }
-      ],
-      "color" : {
-        "color-space" : "srgb",
-        "components" : {
-          "alpha" : "1.000",
-          "blue" : "0.592",
-          "green" : "0.812",
-          "red" : "0.435"
-        }
-      },
-      "idiom" : "universal"
     }
   ],
   "info" : {

+ 0 - 38
Trio/Resources/Assets.xcassets/Colors/LoopPink.colorset/Contents.json

@@ -1,38 +0,0 @@
-{
-  "colors" : [
-    {
-      "color" : {
-        "color-space" : "srgb",
-        "components" : {
-          "alpha" : "1.000",
-          "blue" : "0.796",
-          "green" : "0.750",
-          "red" : "1.000"
-        }
-      },
-      "idiom" : "universal"
-    },
-    {
-      "appearances" : [
-        {
-          "appearance" : "luminosity",
-          "value" : "dark"
-        }
-      ],
-      "color" : {
-        "color-space" : "srgb",
-        "components" : {
-          "alpha" : "1.000",
-          "blue" : "0.796",
-          "green" : "0.750",
-          "red" : "1.000"
-        }
-      },
-      "idiom" : "universal"
-    }
-  ],
-  "info" : {
-    "author" : "xcode",
-    "version" : 1
-  }
-}

+ 0 - 18
Trio/Resources/Assets.xcassets/Colors/LoopRed.colorset/Contents.json

@@ -11,24 +11,6 @@
         }
       },
       "idiom" : "universal"
-    },
-    {
-      "appearances" : [
-        {
-          "appearance" : "luminosity",
-          "value" : "dark"
-        }
-      ],
-      "color" : {
-        "color-space" : "srgb",
-        "components" : {
-          "alpha" : "1.000",
-          "blue" : "0.341",
-          "green" : "0.341",
-          "red" : "0.922"
-        }
-      },
-      "idiom" : "universal"
     }
   ],
   "info" : {

+ 0 - 18
Trio/Resources/Assets.xcassets/Colors/ManualTempBasal.colorset/Contents.json

@@ -11,24 +11,6 @@
         }
       },
       "idiom" : "universal"
-    },
-    {
-      "appearances" : [
-        {
-          "appearance" : "luminosity",
-          "value" : "dark"
-        }
-      ],
-      "color" : {
-        "color-space" : "srgb",
-        "components" : {
-          "alpha" : "1.000",
-          "blue" : "0.988",
-          "green" : "0.588",
-          "red" : "0.118"
-        }
-      },
-      "idiom" : "universal"
     }
   ],
   "info" : {

+ 0 - 18
Trio/Resources/Assets.xcassets/Colors/TabBar.colorset/Contents.json

@@ -11,24 +11,6 @@
         }
       },
       "idiom" : "universal"
-    },
-    {
-      "appearances" : [
-        {
-          "appearance" : "luminosity",
-          "value" : "dark"
-        }
-      ],
-      "color" : {
-        "color-space" : "srgb",
-        "components" : {
-          "alpha" : "1.000",
-          "blue" : "0.950",
-          "green" : "0.550",
-          "red" : "0.490"
-        }
-      },
-      "idiom" : "universal"
     }
   ],
   "info" : {

+ 3 - 21
Trio/Resources/Assets.xcassets/Colors/UAM.colorset/Contents.json

@@ -5,27 +5,9 @@
         "color-space" : "srgb",
         "components" : {
           "alpha" : "1.000",
-          "blue" : "0.271",
-          "green" : "0.518",
-          "red" : "1.000"
-        }
-      },
-      "idiom" : "universal"
-    },
-    {
-      "appearances" : [
-        {
-          "appearance" : "luminosity",
-          "value" : "dark"
-        }
-      ],
-      "color" : {
-        "color-space" : "srgb",
-        "components" : {
-          "alpha" : "1.000",
-          "blue" : "0.271",
-          "green" : "0.518",
-          "red" : "1.000"
+          "blue" : "0.969",
+          "green" : "0.169",
+          "red" : "0.820"
         }
       },
       "idiom" : "universal"

+ 0 - 18
Trio/Resources/Assets.xcassets/Colors/ZT.colorset/Contents.json

@@ -11,24 +11,6 @@
         }
       },
       "idiom" : "universal"
-    },
-    {
-      "appearances" : [
-        {
-          "appearance" : "luminosity",
-          "value" : "dark"
-        }
-      ],
-      "color" : {
-        "color-space" : "srgb",
-        "components" : {
-          "alpha" : "1.000",
-          "blue" : "0.937",
-          "green" : "0.380",
-          "red" : "0.443"
-        }
-      },
-      "idiom" : "universal"
     }
   ],
   "info" : {

+ 0 - 18
Trio/Resources/Assets.xcassets/Colors/minus.colorset/Contents.json

@@ -11,24 +11,6 @@
         }
       },
       "idiom" : "universal"
-    },
-    {
-      "appearances" : [
-        {
-          "appearance" : "luminosity",
-          "value" : "dark"
-        }
-      ],
-      "color" : {
-        "color-space" : "srgb",
-        "components" : {
-          "alpha" : "1.000",
-          "blue" : "0.976",
-          "green" : "0.839",
-          "red" : "0.635"
-        }
-      },
-      "idiom" : "universal"
     }
   ],
   "info" : {

+ 0 - 2
Trio/Sources/Helpers/Color+Extensions.swift

@@ -68,8 +68,6 @@ extension Color {
     static let tempBasal = Color("TempBasal")
     static let basal = Color("Basal")
     static let darkerBlue = Color("DarkerBlue")
-    static let loopPink = Color("LoopPink")
-    static let lemon = Color("Lemon")
     static let minus = Color("minus")
     static let darkGray = Color("darkGray")
     static let darkGreen = Color("darkGreen")

+ 15 - 0
Trio/Sources/Helpers/String+Extensions.swift

@@ -8,6 +8,21 @@ extension String {
     mutating func capitalizeFirstLetter() {
         self = capitalizingFirstLetter()
     }
+
+    func formattedHourMinuteFromTimeString() -> String {
+        let input = DateFormatter()
+        input.dateFormat = "HH:mm:ss"
+
+        let output = DateFormatter()
+        output.timeStyle = .short
+        output.dateStyle = .none
+
+        guard let date = input.date(from: self) else {
+            return self
+        }
+
+        return output.string(from: date)
+    }
 }
 
 extension LosslessStringConvertible {

+ 78 - 0
Trio/Sources/Localizations/Main/Localizable.xcstrings

@@ -36158,6 +36158,9 @@
         }
       }
     },
+    "Allow Fetching From Nightscout" : {
+
+    },
     "Allow Notifications" : {
       "localizations" : {
         "bg" : {
@@ -43696,6 +43699,9 @@
         }
       }
     },
+    "App Version" : {
+
+    },
     "Appearance" : {
       "localizations" : {
         "bg" : {
@@ -51835,6 +51841,9 @@
         }
       }
     },
+    "Basal Rate (%@)" : {
+
+    },
     "Basal Rate Adjustment" : {
       "localizations" : {
         "bg" : {
@@ -57473,6 +57482,9 @@
         }
       }
     },
+    "Branch" : {
+
+    },
     "BRANCH: %@" : {
       "localizations" : {
         "bg" : {
@@ -57591,6 +57603,9 @@
         }
       }
     },
+    "Build Number" : {
+
+    },
     "By default, Trio collects crash reports and other anonymized data related to errors, exceptions, and overall app performance." : {
       "localizations" : {
         "bg" : {
@@ -60731,6 +60746,9 @@
         }
       }
     },
+    "Carb Ratio (%@)" : {
+
+    },
     "Carb Ratios" : {
       "comment" : "Carb Ratios header",
       "localizations" : {
@@ -72837,6 +72855,9 @@
         }
       }
     },
+    "Could not access documents directory" : {
+
+    },
     "Count" : {
       "localizations" : {
         "bg" : {
@@ -107446,6 +107467,18 @@
         }
       }
     },
+    "Export Categories" : {
+
+    },
+    "Export Date" : {
+
+    },
+    "Export Error" : {
+
+    },
+    "Export failed: %@" : {
+
+    },
     "Export logs" : {
       "extractionState" : "manual",
       "localizations" : {
@@ -107696,6 +107729,12 @@
         }
       }
     },
+    "Export Settings" : {
+      "comment" : "Export Settings menu item in Trio Settings Root View"
+    },
+    "Exporting..." : {
+
+    },
     "Extended" : {
       "comment" : "Title string for BeepPreference.extended",
       "extractionState" : "manual",
@@ -110136,6 +110175,9 @@
         }
       }
     },
+    "Failed to write export file: %@" : {
+
+    },
     "Fat" : {
       "comment" : "Add Fat",
       "localizations" : {
@@ -125132,6 +125174,9 @@
         }
       }
     },
+    "High Temptarget Raises Sensitivity" : {
+
+    },
     "High Threshold" : {
       "localizations" : {
         "bg" : {
@@ -133277,6 +133322,9 @@
         }
       }
     },
+    "Indefinite" : {
+
+    },
     "Indicates glucose smoothing is enabled." : {
       "localizations" : {
         "bg" : {
@@ -135692,6 +135740,9 @@
         }
       }
     },
+    "Insulin Type" : {
+
+    },
     "Intercept" : {
       "localizations" : {
         "bg" : {
@@ -137865,6 +137916,12 @@
         }
       }
     },
+    "ISF (%@)" : {
+
+    },
+    "ISF and CR" : {
+
+    },
     "ISF/CR" : {
       "comment" : "Option for both ISF and CR",
       "localizations" : {
@@ -148881,6 +148938,9 @@
         }
       }
     },
+    "Low Temptarget Lowers Sensitivity" : {
+
+    },
     "Low Threshold" : {
       "localizations" : {
         "bg" : {
@@ -158209,6 +158269,9 @@
         }
       }
     },
+    "Metadata" : {
+
+    },
     "Meter glucose" : {
       "comment" : "When adding capillary glucose meater reading",
       "localizations" : {
@@ -168696,6 +168759,9 @@
         }
       }
     },
+    "Not Connected" : {
+
+    },
     "Not enough glucose data. You need at least three glucose readings in the last six hours to run the algorithm." : {
       "localizations" : {
         "bg" : {
@@ -188988,6 +189054,9 @@
         }
       }
     },
+    "Pump Type" : {
+
+    },
     "Quantity Carbs" : {
       "localizations" : {
         "bg" : {
@@ -220630,6 +220699,9 @@
         }
       }
     },
+    "Suspend Zeros IOB" : {
+
+    },
     "suspend-end" : {
       "localizations" : {
         "bg" : {
@@ -223247,6 +223319,9 @@
         }
       }
     },
+    "Target (%@)" : {
+
+    },
     "Target Behavior" : {
       "localizations" : {
         "bg" : {
@@ -246225,6 +246300,9 @@
         }
       }
     },
+    "Trio Backup" : {
+
+    },
     "Trio calculates your current Insulin On Board (IOB) from:" : {
       "localizations" : {
         "bg" : {

+ 21 - 0
Trio/Sources/Models/ExportSetting.swift

@@ -0,0 +1,21 @@
+struct ExportSetting: Codable {
+    let category: String
+    let subcategory: String
+    let name: String
+    let value: String
+    let unit: String
+
+    init(category: String, subcategory: String = "", name: String, value: String, unit: String = "") {
+        self.category = category
+        self.subcategory = subcategory
+        self.name = name
+        self.value = value
+        self.unit = unit
+    }
+}
+
+struct ExportSettingPayload: Codable {
+    let exportFormat: String
+    let exportDate: String
+    let settings: [ExportSetting]
+}

+ 4 - 0
Trio/Sources/Modules/Settings/SettingsStateModel.swift

@@ -1,3 +1,5 @@
+import CoreData
+import Foundation
 import LoopKit
 import LoopKitUI
 import SwiftUI
@@ -10,6 +12,8 @@ extension Settings {
         @Injected() private var nightscoutManager: NightscoutManager!
         @Injected() var pluginManager: PluginManager!
         @Injected() var fetchCgmManager: FetchGlucoseManager!
+        @Injected() private var storage: FileStorage!
+        @Injected() var overrideStorage: OverrideStorage!
 
         @Published var units: GlucoseUnits = .mgdL
         @Published var closedLoop = false

+ 11 - 63
Trio/Sources/Modules/Settings/View/SettingsRootView.swift

@@ -267,6 +267,17 @@ extension Settings {
                         }
                     ).listRowBackground(Color.chart)
 
+                    Section(
+                        header: Text("Trio Backup"),
+                        content: {
+                            Text(String(
+                                localized: "Export Settings",
+                                comment: "Export Settings menu item in Trio Settings Root View"
+                            ))
+                                .navigationLink(to: .settingsExport, from: self)
+                        }
+                    ).listRowBackground(Color.chart)
+
                 } else {
                     Section(
                         header: Text("Search Results"),
@@ -293,69 +304,6 @@ extension Settings {
                         }
                     ).listRowBackground(Color.chart)
                 }
-
-                // TODO: remove this more or less entirely; add build-time flag to enable Middleware; add settings export feature
-//                Section {
-//                    Toggle("Developer Options", isOn: $state.debugOptions)
-//                    if state.debugOptions {
-//                        Group {
-//                            HStack {
-//                                Text("NS Upload Profile and Settings")
-//                                Button("Upload") { state.uploadProfileAndSettings(true) }
-//                                    .frame(maxWidth: .infinity, alignment: .trailing)
-//                                    .buttonStyle(.borderedProminent)
-//                            }
-//                            // Commenting this out for now, as not needed and possibly dangerous for users to be able to nuke their pump pairing informations via the debug menu
-//                            // Leaving it in here, as it may be a handy functionality for further testing or developers.
-//                            // See https://github.com/nightscout/Trio/pull/277 for more information
-//                            //
-//                            //                            HStack {
-//                            //                                Text("Delete Stored Pump State Binary Files")
-//                            //                                Button("Delete") { state.resetLoopDocuments() }
-//                            //                                    .frame(maxWidth: .infinity, alignment: .trailing)
-//                            //                                    .buttonStyle(.borderedProminent)
-//                            //                            }
-//                        }
-//                        Group {
-//                            Text("Preferences")
-//                                .navigationLink(to: .configEditor(file: OpenAPS.Settings.preferences), from: self)
-//                            Text("Pump Settings")
-//                                .navigationLink(to: .configEditor(file: OpenAPS.Settings.settings), from: self)
-//                            Text("Autosense")
-//                                .navigationLink(to: .configEditor(file: OpenAPS.Settings.autosense), from: self)
-//                            //                            Text("Pump History")
-//                            //                                .navigationLink(to: .configEditor(file: OpenAPS.Monitor.pumpHistory), from: self)
-//                            Text("Basal profile")
-//                                .navigationLink(to: .configEditor(file: OpenAPS.Settings.basalProfile), from: self)
-//                    Text("Targets ranges")
-//                        .navigationLink(to: .configEditor(file: OpenAPS.Settings.bgTargets), from: self)
-//                            Text("Temp targets")
-//                                .navigationLink(to: .configEditor(file: OpenAPS.Settings.tempTargets), from: self)
-//                        }
-//
-//                        Group {
-//                            Text("Pump profile")
-//                                .navigationLink(to: .configEditor(file: OpenAPS.Settings.pumpProfile), from: self)
-//                            Text("Profile")
-//                                .navigationLink(to: .configEditor(file: OpenAPS.Settings.profile), from: self)
-//                            //                            Text("Carbs")
-//                            //                                .navigationLink(to: .configEditor(file: OpenAPS.Monitor.carbHistory), from: self)
-//                        }
-//
-//                        Group {
-//                            Text("Target presets")
-//                                .navigationLink(to: .configEditor(file: OpenAPS.Trio.tempTargetsPresets), from: self)
-//                            Text("Calibrations")
-//                                .navigationLink(to: .configEditor(file: OpenAPS.Trio.calibrations), from: self)
-//                            Text("Middleware")
-//                                .navigationLink(to: .configEditor(file: OpenAPS.Middleware.determineBasal), from: self)
-//                            //                            Text("Statistics")
-//                            //                                .navigationLink(to: .configEditor(file: OpenAPS.Monitor.statistics), from: self)
-//                            Text("Edit settings json")
-//                                .navigationLink(to: .configEditor(file: OpenAPS.Trio.settings), from: self)
-//                        }
-//                    }
-//                }.listRowBackground(Color.chart)
             }
             .scrollContentBackground(.hidden).background(appState.trioBackgroundColor(for: colorScheme))
             .sheet(isPresented: $shouldDisplayHint) {

+ 5 - 0
Trio/Sources/Modules/SettingsExport/SettingsExportDataFlow.swift

@@ -0,0 +1,5 @@
+enum SettingsExport {
+    enum Config {}
+}
+
+protocol SettingsExportProvider: Provider {}

+ 3 - 0
Trio/Sources/Modules/SettingsExport/SettingsExportProvider.swift

@@ -0,0 +1,3 @@
+extension SettingsExport {
+    final class Provider: BaseProvider, SettingsExportProvider {}
+}

Разлика између датотеке није приказан због своје велике величине
+ 1329 - 0
Trio/Sources/Modules/SettingsExport/SettingsExportStateModel.swift


+ 192 - 0
Trio/Sources/Modules/SettingsExport/View/SettingsExportRootView.swift

@@ -0,0 +1,192 @@
+import SwiftUI
+import Swinject
+
+extension SettingsExport {
+    struct RootView: BaseView {
+        let resolver: Resolver
+        @StateObject var state = StateModel()
+
+        @State private var showSettingsExport = false
+        @State private var showExportError = false
+        @State private var exportErrorMessage = ""
+        @State private var exportedFileURL: URL?
+
+        @Environment(\.colorScheme) var colorScheme
+        @Environment(AppState.self) var appState
+
+        var body: some View {
+            List {
+                Section(
+                    header: Text("Export Categories"),
+                    content: {
+                        // Select All toggle
+                        HStack {
+                            Button(action: {
+                                state.toggleAllCategories(!state.allCategoriesSelected)
+                            }) {
+                                HStack {
+                                    Image(systemName: state.allCategoriesSelected ? "checkmark.square.fill" : "square")
+                                        .foregroundColor(state.allCategoriesSelected ? .blue : .secondary)
+                                    Text(
+                                        state
+                                            .allCategoriesSelected ? String(localized: "Deselect All") :
+                                            String(localized: "Select All")
+                                    )
+                                    .fontWeight(.bold)
+                                    .foregroundColor(.primary)
+                                    Spacer()
+                                }
+                            }
+                            .buttonStyle(PlainButtonStyle())
+                        }
+
+                        // Individual category toggles
+                        ForEach(SettingsExport.StateModel.ExportCategory.allCases) { category in
+                            HStack {
+                                Button(action: {
+                                    if state.selectedCategories.contains(category) {
+                                        state.selectedCategories.remove(category)
+                                    } else {
+                                        state.selectedCategories.insert(category)
+                                    }
+                                }) {
+                                    HStack {
+                                        Image(
+                                            systemName: state.selectedCategories
+                                                .contains(category) ? "checkmark.square.fill" : "square"
+                                        )
+                                        .foregroundColor(state.selectedCategories.contains(category) ? .blue : .secondary)
+
+                                        Text(category.rawValue)
+
+                                        Spacer()
+                                    }
+                                }
+                                .buttonStyle(PlainButtonStyle())
+                            }
+                            .padding(.vertical, 2)
+                        }
+                    }
+                ).listRowBackground(Color.chart)
+
+                Section {
+                    Button(action: {
+                        Task {
+                            let impactHeavy = UIImpactFeedbackGenerator(style: .heavy)
+                            impactHeavy.impactOccurred()
+                            state.isExporting = true
+
+                            switch await state.exportSelectedSettings() {
+                            case let .success(fileURL):
+                                if FileManager.default.fileExists(atPath: fileURL.path) {
+                                    do {
+                                        let attributes = try FileManager.default.attributesOfItem(atPath: fileURL.path)
+                                        let fileSize = attributes[.size] as? Int ?? 0
+
+                                        if fileSize > 0 {
+                                            exportedFileURL = fileURL
+                                            // Stop spinner on successful export
+                                            state.isExporting = false
+                                            showSettingsExport = true
+                                        } else {
+                                            exportErrorMessage = "Export file is empty (0 bytes)"
+                                            showExportError = true
+                                            state.isExporting = false
+                                        }
+                                    } catch {
+                                        exportErrorMessage = "Could not verify file attributes: \(error.localizedDescription)"
+                                        showExportError = true
+                                        // Stop spinner on error
+                                        state.isExporting = false
+                                    }
+                                } else {
+                                    exportErrorMessage = "Export file was created but could not be found at: \(fileURL.path)"
+                                    showExportError = true
+                                    // Stop spinner on error
+                                    state.isExporting = false
+                                }
+                            case let .failure(error):
+                                exportErrorMessage = error.localizedDescription
+                                showExportError = true
+                                // Stop spinner on error
+                                state.isExporting = false
+                            }
+                        }
+                    }, label: {
+                        if state.isExporting {
+                            HStack {
+                                ProgressView().padding(.trailing, 10)
+                                Text("Exporting...")
+                            }
+                        } else {
+                            Text("Export Settings")
+                        }
+
+                    })
+                        .disabled(state.selectedCategories.isEmpty)
+                        .frame(maxWidth: .infinity, alignment: .center)
+                        .tint(.white)
+                }.listRowBackground(
+                    state.selectedCategories.isEmpty ? Color(.systemGray4) : Color(.systemBlue)
+                )
+            }
+            .listSectionSpacing(sectionSpacing)
+            .scrollContentBackground(.hidden).background(appState.trioBackgroundColor(for: colorScheme))
+            .onAppear(perform: configureView)
+            .navigationTitle("Export Settings")
+            .navigationBarTitleDisplayMode(.automatic)
+//            // TODO: implement help sheet
+//            .toolbar {
+//                ToolbarItem(placement: .topBarTrailing) {
+//                    Button(
+//                        action: {
+//                            state.isHelpSheetPresented.toggle()
+//                        },
+//                        label: {
+//                            Image(systemName: "questionmark.circle")
+//                        }
+//                    )
+//                }
+//            }
+//            .sheet(isPresented: $state.isHelpSheetPresented) {
+//                NavigationStack {
+//                    List {
+//                        Text("Hello World!")
+//                    }
+//                }
+//                .padding()
+//                .presentationDetents(
+//                    [.fraction(0.9), .large],
+//                    selection: $state.helpSheetDetent
+//                )
+//            }
+            .sheet(isPresented: $showSettingsExport) {
+                if let fileURL = exportedFileURL {
+                    ShareSheet(activityItems: [fileURL])
+                }
+            }
+            .alert("Export Error", isPresented: $showExportError) {
+                Button("OK", role: .cancel) {}
+            } message: {
+                Text(exportErrorMessage)
+            }
+        }
+    }
+}
+
+private struct ExportCategoryRow: View {
+    let title: String
+    let description: String
+
+    var body: some View {
+        VStack(alignment: .leading, spacing: 4) {
+            Text(title)
+                .font(.subheadline)
+                .fontWeight(.medium)
+            Text(description)
+                .font(.caption)
+                .foregroundColor(.secondary)
+        }
+        .padding(.vertical, 2)
+    }
+}

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

@@ -49,6 +49,7 @@ enum Screen: Identifiable, Hashable {
     case algorithmAdvancedSettings
     case unitsAndLimits
     case appDiagnostics
+    case settingsExport
 
     var id: Int { String(reflecting: self).hashValue }
 }
@@ -162,6 +163,8 @@ extension Screen {
             UnitsLimitsSettings.RootView(resolver: resolver)
         case .appDiagnostics:
             AppDiagnostics.RootView(resolver: resolver)
+        case .settingsExport:
+            SettingsExport.RootView(resolver: resolver)
         }
     }
 

+ 59 - 0
TrioTests/SettingsExportTests.swift

@@ -0,0 +1,59 @@
+@testable import Trio
+import XCTest
+
+final class SettingsExportTests: XCTestCase {
+    func testCSVEscaping() {
+        // Test CSV escaping functionality
+        let testValue = "Test,Value\"With\nSpecial Characters"
+        let escaped = csvEscape(testValue)
+        let expected = "\"Test,Value\"\"With\nSpecial Characters\""
+        XCTAssertEqual(escaped, expected, "CSV escaping should handle commas, quotes, and newlines")
+    }
+
+    func testCSVEscapingSimple() {
+        // Test simple values don't get escaped
+        let testValue = "SimpleValue"
+        let escaped = csvEscape(testValue)
+        XCTAssertEqual(escaped, testValue, "Simple values should not be escaped")
+    }
+
+    func testExportCSVStructure() {
+        // Test that the CSV has the expected header structure
+        let expectedHeader = "Setting Category,Subcategory,Setting Name,Value,Unit"
+        // This test would require mocking the settings manager and file storage
+        // For now, we verify the header format is correct
+        XCTAssertEqual(expectedHeader.components(separatedBy: ",").count, 5, "CSV header should have 5 columns")
+    }
+
+    func testExportErrorTypes() {
+        // Test that our export error types are properly defined
+        let documentError = Settings.StateModel.ExportError.documentsDirectoryNotFound
+        XCTAssertNotNil(documentError.errorDescription, "Document error should have description")
+
+        let writeError = Settings.StateModel.ExportError.fileWriteError(TestError.testError)
+        XCTAssertNotNil(writeError.errorDescription, "Write error should have description")
+
+        let unknownError = Settings.StateModel.ExportError.unknown("Test message")
+        XCTAssertNotNil(unknownError.errorDescription, "Unknown error should have description")
+    }
+
+    func testExportFileNaming() {
+        // Test that export files have the correct naming pattern
+        let formatter = DateFormatter()
+        formatter.dateFormat = "yyyyMMdd_HHmmss"
+        let timestamp = formatter.string(from: Date())
+        let fileName = "TrioSettings_\(timestamp).csv"
+
+        XCTAssertTrue(fileName.hasPrefix("TrioSettings_"), "File name should start with TrioSettings_")
+        XCTAssertTrue(fileName.hasSuffix(".csv"), "File name should end with .csv")
+        XCTAssertEqual(fileName.components(separatedBy: "_").count, 2, "File name should have one underscore")
+    }
+
+    // Helper function to test CSV escaping (extracted from Settings.StateModel)
+    private func csvEscape(_ value: String) -> String {
+        if value.contains(",") || value.contains("\"") || value.contains("\n") {
+            return "\"\(value.replacingOccurrences(of: "\"", with: "\"\""))\""
+        }
+        return value
+    }
+}