Просмотр исходного кода

Merge branch 'UI' into UI-superBolus

polscm32 2 лет назад
Родитель
Сommit
40b9d6736a
54 измененных файлов с 1084 добавлено и 349 удалено
  1. 9 4
      .github/workflows/add_identifiers.yml
  2. 206 11
      .github/workflows/build_iAPS.yml
  3. 12 6
      .github/workflows/create_certs.yml
  4. 162 38
      .github/workflows/validate_secrets.yml
  5. 1 0
      Core_Data.xcdatamodeld/Core_Data.xcdatamodel/contents
  6. 15 15
      Dependencies/OmniBLE/Localizations/de.lproj/Localizable.strings
  7. 15 15
      Dependencies/OmniBLE/Localizations/it.lproj/Localizable.strings
  8. 1 1
      Dependencies/OmniBLE/Localizations/nl.lproj/Localizable.strings
  9. 15 15
      Dependencies/OmniBLE/Localizations/ru.lproj/Localizable.strings
  10. 2 2
      Dependencies/OmniBLE/Localizations/uk.lproj/Localizable.strings
  11. 2 2
      Dependencies/OmniKit/OmniKit/Resources/de.lproj/Localizable.strings
  12. 2 2
      Dependencies/OmniKit/OmniKit/Resources/it.lproj/Localizable.strings
  13. 2 2
      Dependencies/OmniKit/OmniKit/Resources/ru.lproj/Localizable.strings
  14. 14 14
      Dependencies/OmniKit/OmniKitUI/Resources/de.lproj/Localizable.strings
  15. 14 14
      Dependencies/OmniKit/OmniKitUI/Resources/it.lproj/Localizable.strings
  16. 2 2
      Dependencies/OmniKit/OmniKitUI/Resources/nl.lproj/Localizable.strings
  17. 14 14
      Dependencies/OmniKit/OmniKitUI/Resources/ru.lproj/Localizable.strings
  18. 2 2
      Dependencies/OmniKit/OmniKitUI/Resources/uk.lproj/Localizable.strings
  19. 1 1
      FreeAPS/Resources/javascript/bundle/meal.js
  20. 8 6
      FreeAPS/Sources/APS/Storage/CarbsStorage.swift
  21. 21 0
      FreeAPS/Sources/Localizations/Main/ar.lproj/Localizable.strings
  22. 23 2
      FreeAPS/Sources/Localizations/Main/da.lproj/Localizable.strings
  23. 28 7
      FreeAPS/Sources/Localizations/Main/de.lproj/Localizable.strings
  24. 22 1
      FreeAPS/Sources/Localizations/Main/es.lproj/Localizable.strings
  25. 21 0
      FreeAPS/Sources/Localizations/Main/fi.lproj/Localizable.strings
  26. 22 1
      FreeAPS/Sources/Localizations/Main/fr.lproj/Localizable.strings
  27. 21 0
      FreeAPS/Sources/Localizations/Main/he.lproj/Localizable.strings
  28. 28 7
      FreeAPS/Sources/Localizations/Main/it.lproj/Localizable.strings
  29. 24 3
      FreeAPS/Sources/Localizations/Main/nb.lproj/Localizable.strings
  30. 50 21
      FreeAPS/Sources/Localizations/Main/nl.lproj/Localizable.strings
  31. 21 0
      FreeAPS/Sources/Localizations/Main/pl.lproj/Localizable.strings
  32. 22 1
      FreeAPS/Sources/Localizations/Main/pt-BR.lproj/Localizable.strings
  33. 21 0
      FreeAPS/Sources/Localizations/Main/pt-PT.lproj/Localizable.strings
  34. 27 6
      FreeAPS/Sources/Localizations/Main/ru.lproj/Localizable.strings
  35. 21 0
      FreeAPS/Sources/Localizations/Main/sk.lproj/Localizable.strings
  36. 7 1
      FreeAPS/Sources/Localizations/Main/sv.lproj/Localizable.strings
  37. 23 2
      FreeAPS/Sources/Localizations/Main/tr.lproj/Localizable.strings
  38. 21 0
      FreeAPS/Sources/Localizations/Main/uk.lproj/Localizable.strings
  39. 22 1
      FreeAPS/Sources/Localizations/Main/zh-Hans.lproj/Localizable.strings
  40. 2 0
      FreeAPS/Sources/Models/CarbsEntry.swift
  41. 6 2
      FreeAPS/Sources/Modules/AddCarbs/AddCarbsStateModel.swift
  42. 2 5
      FreeAPS/Sources/Modules/AddCarbs/View/AddCarbsRootView.swift
  43. 10 2
      FreeAPS/Sources/Modules/Bolus/BolusStateModel.swift
  44. 10 4
      FreeAPS/Sources/Modules/DataTable/DataTableStateModel.swift
  45. 0 9
      FreeAPS/Sources/Modules/Home/HomeStateModel.swift
  46. 10 2
      FreeAPS/Sources/Modules/Home/View/Chart/MainChartView.swift
  47. 11 34
      FreeAPS/Sources/Modules/Home/View/HomeRootView.swift
  48. 4 0
      FreeAPS/Sources/Modules/NightscoutConfig/NightscoutConfigStateModel.swift
  49. 3 3
      FreeAPS/Sources/Services/HealthKit/HealthKitManager.swift
  50. 1 1
      FreeAPS/Sources/Services/Network/NightscoutAPI.swift
  51. 1 0
      FreeAPS/Sources/Services/WatchManager/WatchManager.swift
  52. 1 0
      FreeAPS/Sources/Shortcuts/Carbs/CarbPresetIntentRequest.swift
  53. 72 68
      Gemfile.lock
  54. 7 0
      fastlane/Fastfile

+ 9 - 4
.github/workflows/add_identifiers.yml

@@ -1,15 +1,16 @@
 name: 2. Add Identifiers
-run-name: Add Identifiers
+run-name: Add Identifiers (${{ github.ref_name }})
 on:
   workflow_dispatch:
 
 jobs:
-  secrets:
+  validate:
+    name: Validate
     uses: ./.github/workflows/validate_secrets.yml
     secrets: inherit
 
   identifiers:
-    needs: secrets
+    needs: validate
     runs-on: macos-13
     steps:
       # Uncomment to manually select Xcode version if needed
@@ -24,9 +25,13 @@ jobs:
       - name: Patch Match Tables
         run: find /usr/local/lib/ruby/gems -name table_printer.rb | xargs sed -i "" "/puts(Terminal::Table.new(params))/d"
         
+      # Install project dependencies
+      - name: Install Project Dependencies
+        run: bundle install
+
       # Create or update identifiers for app
       - name: Fastlane Provision
-        run: fastlane identifiers
+        run: bundle exec fastlane identifiers
         env:
           TEAMID: ${{ secrets.TEAMID }}
           GH_PAT: ${{ secrets.GH_PAT }}

+ 206 - 11
.github/workflows/build_iAPS.yml

@@ -6,37 +6,232 @@ on:
   ## Remove the "#" sign from the beginning of the line below to get automated builds on push (code changes in your repository)
   #push:
   
-  ## Remove the "#" sign from the beginning of the two lines below to get automated builds every two months
-  #schedule:
-    #- cron: '0 17 1 */2 *' # Runs at 17:00 UTC on the 1st in Jan, Mar, May, Jul, Sep and Nov.
+  schedule:
+    #- cron: '30 04 1 * *' # Runs at 04:30 UTC on the 1st every month
+    - cron: '0 8 * * 3' # Checks for updates at 08:00 UTC every Wednesday
+    - cron: '0 6 1 * *' # Builds the app on the 1st of every month at 06:00 UTC
 
+env:  
+  UPSTREAM_REPO: Artificial-Pancreas/iAPS
+  UPSTREAM_BRANCH: ${{ github.ref_name }} # branch on upstream repository to sync from (replace with specific branch name if needed)
+  TARGET_BRANCH: ${{ github.ref_name }} # target branch on fork to be kept in sync, and target branch on upstream to be kept alive (replace with specific branch name if needed)
+  ALIVE_BRANCH: alive
 
 jobs:
-  secrets:
+  validate:
+    name: Validate
     uses: ./.github/workflows/validate_secrets.yml
     secrets: inherit
 
+  # Checks if GH_PAT holds workflow permissions
+  # Checks for existence of alive branch; if non-existent creates it
+  check_alive_and_permissions:
+    needs: validate
+    runs-on: ubuntu-latest
+    name: Check alive branch and permissions
+    permissions:
+      contents: write
+    outputs:
+      WORKFLOW_PERMISSION: ${{ steps.workflow-permission.outputs.has_permission }}
+    
+    steps:
+    - name: Check for workflow permissions
+      id: workflow-permission
+      env: 
+        TOKEN_TO_CHECK: ${{ secrets.GH_PAT }}
+      run: |
+        PERMISSIONS=$(curl -sS -f -I -H "Authorization: token ${{ env.TOKEN_TO_CHECK }}" https://api.github.com | grep ^x-oauth-scopes: | cut -d' ' -f2-);
+        
+        if [[ $PERMISSIONS =~ "workflow" || $PERMISSIONS == "" ]]; then
+          echo "GH_PAT holds workflow permissions or is fine-grained PAT."
+          echo "has_permission=true" >> $GITHUB_OUTPUT # Set WORKFLOW_PERMISSION to false.
+        else 
+          echo "GH_PAT lacks workflow permissions."
+          echo "Automated build features will be skipped!"
+          echo "has_permission=false" >> $GITHUB_OUTPUT # Set WORKFLOW_PERMISSION to false.
+        fi
+    
+    - name: Check for alive branch
+      if: steps.workflow-permission.outputs.has_permission == 'true'
+      env:
+        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+      run: |
+        if [[ "$(gh api -H "Accept: application/vnd.github+json" /repos/${{ github.repository_owner }}/iAPS/branches | jq --raw-output 'any(.name=="alive")')" == "true" ]]; then
+          echo "Branch 'alive' exists."
+          echo "ALIVE_BRANCH_EXISTS=true" >> $GITHUB_ENV # Set ALIVE_BRANCH_EXISTS to true
+        else
+          echo "Branch 'alive' does not exist."
+          echo "ALIVE_BRANCH_EXISTS=false" >> $GITHUB_ENV # Set ALIVE_BRANCH_EXISTS to false
+        fi
+    
+    - name: Create alive branch
+      if: env.ALIVE_BRANCH_EXISTS == 'false'
+      env:
+        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+      run: |
+        # Get ref for Artificial-Pancreas/iAPS:dev
+        SHA=$(curl -sS https://api.github.com/repos/${{ env.UPSTREAM_REPO }}/git/refs \
+          | jq '.[] | select(.ref == "refs/heads/dev" ) | .object.sha' \
+          | tr -d '"'
+        );
+        
+        # Create alive branch based on Artificial-Pancreas/iAPS:dev
+        gh api \
+          --method POST \
+          -H "Authorization: token $GITHUB_TOKEN" \
+          -H "Accept: application/vnd.github.v3+json" \
+          /repos/${{ github.repository_owner }}/iAPS/git/refs \
+          -f ref='refs/heads/alive' \
+          -f sha=$SHA
+  
+  # Checks for changes in upstream repository; if changes exist prompts sync for build
+  # Performs keepalive to avoid stale fork
+  check_latest_from_upstream:
+    needs: [validate, check_alive_and_permissions]
+    runs-on: ubuntu-latest
+    name: Check upstream and keep alive
+    outputs: 
+      NEW_COMMITS: ${{ steps.sync.outputs.has_new_commits }}
+    
+    steps:
+    - name: Checkout target repo
+      if: |
+        needs.check_alive_and_permissions.outputs.WORKFLOW_PERMISSION == 'true' &&
+        (vars.SCHEDULED_BUILD != 'false' || vars.SCHEDULED_SYNC != 'false')
+      uses: actions/checkout@v3
+      with:
+        token: ${{ secrets.GH_PAT }}
+        ref: alive
+    
+    - name: Sync upstream changes
+      if: | # do not run the upstream sync action on the upstream repository
+        needs.check_alive_and_permissions.outputs.WORKFLOW_PERMISSION == 'true' &&
+        vars.SCHEDULED_SYNC != 'false' && github.repository_owner != 'Artificial-Pancreas'
+      id: sync
+      uses: aormsby/Fork-Sync-With-Upstream-action@v3.4
+      with:
+        target_sync_branch: ${{ env.ALIVE_BRANCH }}
+        shallow_since: 6 months ago
+        target_repo_token: ${{ secrets.GH_PAT }}
+        upstream_sync_branch: ${{ env.UPSTREAM_BRANCH }}
+        upstream_sync_repo: ${{ env.UPSTREAM_REPO }}
+    
+    # Display a sample message based on the sync output var 'has_new_commits'
+    - name: New commits found
+      if: |
+        needs.check_alive_and_permissions.outputs.WORKFLOW_PERMISSION == 'true' &&
+        vars.SCHEDULED_SYNC != 'false' && steps.sync.outputs.has_new_commits == 'true'
+      run: echo "New commits were found to sync."
+    
+    - name: No new commits
+      if: |
+        needs.check_alive_and_permissions.outputs.WORKFLOW_PERMISSION == 'true' && 
+        vars.SCHEDULED_SYNC != 'false' && steps.sync.outputs.has_new_commits == 'false'
+      run: echo "There were no new commits."
+    
+    - name: Show value of 'has_new_commits'
+      if: needs.check_alive_and_permissions.outputs.WORKFLOW_PERMISSION == 'true' && vars.SCHEDULED_SYNC != 'false'
+      run: |
+        echo ${{ steps.sync.outputs.has_new_commits }}
+        echo "NEW_COMMITS=${{ steps.sync.outputs.has_new_commits }}" >> $GITHUB_OUTPUT
+    
+    # Keep repository "alive": add empty commits to ALIVE_BRANCH after "time_elapsed" days of inactivity to avoid inactivation of scheduled workflows
+    - name: Keep alive
+      if: |
+        needs.check_alive_and_permissions.outputs.WORKFLOW_PERMISSION == 'true' &&
+        (vars.SCHEDULED_BUILD != 'false' || vars.SCHEDULED_SYNC != 'false')
+      uses: gautamkrishnar/keepalive-workflow@v1 # using the workflow with default settings
+      with:
+        time_elapsed: 20 # Time elapsed from the previous commit to trigger a new automated commit (in days)
+    
+    - name: Show scheduled build configuration message
+      if: needs.check_alive_and_permissions.outputs.WORKFLOW_PERMISSION != 'true'
+      run: |
+        echo "### :calendar: Scheduled Sync and Build Disabled :mobile_phone_off:" >> $GITHUB_STEP_SUMMARY
+        echo "You have not yet configured the scheduled sync and build for iAPS's browser build." >> $GITHUB_STEP_SUMMARY
+        echo "Synchronizing your fork of <code>iAPS</code> with the upstream repository <code>Artificial-Pancreas/iAPS</code> will be skipped." >> $GITHUB_STEP_SUMMARY
+        echo "If you want to enable automatic builds and updates for your iAPS, please follow the instructions \
+              under the following path <code>iAPS/fastlane/testflight.md</code>." >> $GITHUB_STEP_SUMMARY
+   
+  
+  # Builds iAPS
   build:
-    needs: secrets
+    name: Build
+    needs: [validate, check_alive_and_permissions, check_latest_from_upstream]
     runs-on: macos-13
+    permissions:
+      contents: write
+    if: | # runs if started manually, or if sync schedule is set and enabled and scheduled on the first Saturday each month, or if sync schedule is set and enabled and new commits were found
+        github.event_name == 'workflow_dispatch' ||
+        (needs.check_alive_and_permissions.outputs.WORKFLOW_PERMISSION == 'true' &&
+          (vars.SCHEDULED_BUILD != 'false' && github.event.schedule == '0 6 1 * *') ||
+          (vars.SCHEDULED_SYNC == 'true' && needs.check_latest_from_upstream.outputs.NEW_COMMITS == 'true' )
+        )
     steps:
-      # Uncomment to manually select Xcode version if needed
       - name: Select Xcode version
         run: "sudo xcode-select --switch /Applications/Xcode_15.0.1.app/Contents/Developer"
+      
+      - name: Checkout Repo for syncing
+        if: |
+          needs.check_alive_and_permissions.outputs.WORKFLOW_PERMISSION == 'true' &&
+          vars.SCHEDULED_SYNC == 'true'
+        uses: actions/checkout@v3
+        with:
+          token: ${{ secrets.GH_PAT }}
+          ref: ${{ env.TARGET_BRANCH }} 
+      
+      - name: Sync upstream changes
+        if: | # do not run the upstream sync action on the upstream repository
+          needs.check_alive_and_permissions.outputs.WORKFLOW_PERMISSION == 'true' &&
+          vars.SCHEDULED_SYNC == 'true' && github.repository_owner != 'Artificial-Pancreas'
+        id: sync
+        uses: aormsby/Fork-Sync-With-Upstream-action@v3.4
+        with:
+          target_sync_branch: ${{ env.TARGET_BRANCH }}
+          shallow_since: 6 months ago
+          target_repo_token: ${{ secrets.GH_PAT }}
+          upstream_sync_branch: ${{ env.UPSTREAM_BRANCH }}
+          upstream_sync_repo: ${{ env.UPSTREAM_REPO }}
+      
+      # Display a sample message based on the sync output var 'has_new_commits'
+      - name: New commits found
+        if: |
+          needs.check_alive_and_permissions.outputs.WORKFLOW_PERMISSION == 'true' &&
+          vars.SCHEDULED_SYNC == 'true' && steps.sync.outputs.has_new_commits == 'true'
+        run: echo "New commits were found to sync."
+    
+      - name: No new commits
+        if: |
+          needs.check_alive_and_permissions.outputs.WORKFLOW_PERMISSION == 'true' && 
+          vars.SCHEDULED_SYNC == 'true' && steps.sync.outputs.has_new_commits == 'false'
+        run: echo "There were no new commits."
+      
+      - name: Show value of 'has_new_commits'
+        if: |
+          needs.check_alive_and_permissions.outputs.WORKFLOW_PERMISSION == 'true'
+          && vars.SCHEDULED_SYNC == 'true'
+        run: |
+          echo ${{ steps.sync.outputs.has_new_commits }}
+          echo "NEW_COMMITS=${{ steps.sync.outputs.has_new_commits }}" >> $GITHUB_OUTPUT
 
-      # Checks-out the repo
-      - name: Checkout Repo
+      - name: Checkout Repo for building
         uses: actions/checkout@v3
         with:
+          token: ${{ secrets.GH_PAT }}
           submodules: recursive
-      
+          ref: ${{ env.TARGET_BRANCH }}
+
       # Patch Fastlane Match to not print tables
       - name: Patch Match Tables
         run: find /usr/local/lib/ruby/gems -name table_printer.rb | xargs sed -i "" "/puts(Terminal::Table.new(params))/d"
       
+      # Install project dependencies
+      - name: Install project dependencies
+        run: bundle install
+      
       # Build signed iAPS IPA file
       - name: Fastlane Build & Archive
-        run: fastlane build_iAPS
+        run: bundle exec fastlane build_iAPS        
         env:
           TEAMID: ${{ secrets.TEAMID }}
           GH_PAT: ${{ secrets.GH_PAT }}
@@ -47,7 +242,7 @@ jobs:
       
       # Upload to TestFlight
       - name: Fastlane upload to TestFlight
-        run: fastlane release
+        run: bundle exec fastlane release
         env:
           TEAMID: ${{ secrets.TEAMID }}
           GH_PAT: ${{ secrets.GH_PAT }}

+ 12 - 6
.github/workflows/create_certs.yml

@@ -1,15 +1,17 @@
 name: 3. Create Certificates
-run-name: Create Certificates
+run-name: Create Certificates (${{ github.ref_name }})
 on:
   workflow_dispatch:
 
 jobs:
-  secrets:
+  validate:
+    name: Validate
     uses: ./.github/workflows/validate_secrets.yml
     secrets: inherit
-
+  
   certificates:
-    needs: secrets
+    name: Create Certificates
+    needs: validate
     runs-on: macos-13
     steps:
       # Uncomment to manually select Xcode version if needed
@@ -23,10 +25,14 @@ jobs:
       # Patch Fastlane Match to not print tables
       - name: Patch Match Tables
         run: find /usr/local/lib/ruby/gems -name table_printer.rb | xargs sed -i "" "/puts(Terminal::Table.new(params))/d"
-        
+
+      # Install project dependencies
+      - name: Install Project Dependencies
+        run: bundle install
+
       # Create or update certificates for app
       - name: Create Certificates
-        run: fastlane certs
+        run: bundle exec fastlane certs
         env:
           TEAMID: ${{ secrets.TEAMID }}
           GH_PAT: ${{ secrets.GH_PAT }}

+ 162 - 38
.github/workflows/validate_secrets.yml

@@ -1,70 +1,194 @@
 name: 1. Validate Secrets
-run-name: Validate Secrets
+run-name: Validate Secrets (${{ github.ref_name }})
 on: [workflow_call, workflow_dispatch]
 
 jobs:
-  validate:
+  validate-access-token:
+    name: Access
     runs-on: macos-13
+    env:
+      GH_PAT: ${{ secrets.GH_PAT }}
+      GH_TOKEN: ${{ secrets.GH_PAT }}
+    outputs:
+      HAS_WORKFLOW_PERMISSION: ${{ steps.access-token.outputs.has_workflow_permission }}
+    steps:
+      - name: Validate Access Token
+        id: access-token
+        run: |
+          # Validate Access Token
+          
+          # Ensure that gh exit codes are handled when output is piped.
+          set -o pipefail
+          
+          # Define patterns to validate the access token (GH_PAT) and distinguish between classic and fine-grained tokens.
+          GH_PAT_CLASSIC_PATTERN='^ghp_[a-zA-Z0-9]{36}$'
+          GH_PAT_FINE_GRAINED_PATTERN='^github_pat_[a-zA-Z0-9]{22}_[a-zA-Z0-9]{59}$'
+          
+          # Validate Access Token (GH_PAT)
+          if [ -z "$GH_PAT" ]; then
+            failed=true
+            echo "::error::The GH_PAT secret is unset or empty. Set it and try again."
+          else
+            if [[ $GH_PAT =~ $GH_PAT_CLASSIC_PATTERN ]]; then
+              provides_scopes=true
+              echo "The GH_PAT secret is a structurally valid classic token."
+            elif [[ $GH_PAT =~ $GH_PAT_FINE_GRAINED_PATTERN ]]; then
+              echo "The GH_PAT secret is a structurally valid fine-grained token."
+            else
+              unknown_format=true
+              echo "The GH_PAT secret does not have a known token format."
+            fi
+            
+            # Attempt to capture the x-oauth-scopes scopes of the token.
+            if ! scopes=$(curl -sS -f -I -H "Authorization: token $GH_PAT" https://api.github.com | { grep -i '^x-oauth-scopes:' || true; } | cut -d ' ' -f2- | tr -d '\r'); then
+              failed=true
+              if [ $unknown_format ]; then
+                echo "::error::Unable to connect to GitHub using the GH_PAT secret. Verify that it is set correctly (including the 'ghp_' or 'github_pat_' prefix) and try again."
+              else
+                echo "::error::Unable to connect to GitHub using the GH_PAT secret. Verify that the token exists and has not expired at https://github.com/settings/tokens. If necessary, regenerate or create a new token (and update the secret), then try again."
+              fi
+            elif [[ $scopes =~ workflow ]]; then
+              echo "The GH_PAT secret has repo and workflow permissions."
+              echo "has_workflow_permission=true" >> $GITHUB_OUTPUT
+            elif [[ $scopes =~ repo ]]; then
+              echo "The GH_PAT secret has repo (but not workflow) permissions."
+            elif [ $provides_scopes ]; then
+              failed=true
+              if [ -z "$scopes" ]; then
+                echo "The GH_PAT secret is valid and can be used to connect to GitHub, but it does not provide any permission scopes."
+              else
+                echo "The GH_PAT secret is valid and can be used to connect to GitHub, but it only provides the following permission scopes: $scopes"
+              fi
+              echo "::error::The GH_PAT secret is lacking at least the 'repo' permission scope required to access the Match-Secrets repository. Update the token permissions at https://github.com/settings/tokens (to include the 'repo' and 'workflow' scopes) and try again."
+            else
+              echo "The GH_PAT secret is valid and can be used to connect to GitHub, but it does not provide inspectable scopes. Assuming that the 'repo' and 'workflow' permission scopes required to access the Match-Secrets repository and perform automations are present."
+              echo "has_workflow_permission=true" >> $GITHUB_OUTPUT
+            fi
+          fi
+          
+          # Exit unsuccessfully if secret validation failed.
+          if [ $failed ]; then
+            exit 2
+          fi
+  
+  validate-match-secrets:
+    name: Match-Secrets
+    needs: validate-access-token
+    runs-on: macos-13
+    env:
+      GH_TOKEN: ${{ secrets.GH_PAT }}
+    steps:
+      - name: Validate Match-Secrets
+        run: |
+          # Validate Match-Secrets
+          
+          # Ensure that gh exit codes are handled when output is piped.
+          set -o pipefail
+          
+          # If a Match-Secrets repository does not exist, attempt to create one.
+          if ! visibility=$(gh repo view ${{ github.repository_owner }}/Match-Secrets --json visibility | jq --raw-output '.visibility | ascii_downcase'); then
+            echo "A '${{ github.repository_owner }}/Match-Secrets' repository could not be found using the GH_PAT secret. Attempting to create one..."
+            
+            # Create a private Match-Secrets repository and verify that it exists and that it is private.
+            if gh repo create ${{ github.repository_owner }}/Match-Secrets --private >/dev/null && [ "$(gh repo view ${{ github.repository_owner }}/Match-Secrets --json visibility | jq --raw-output '.visibility | ascii_downcase')" == "private" ]; then
+              echo "Created a private '${{ github.repository_owner }}/Match-Secrets' repository."
+            else
+              failed=true
+              echo "::error::Unable to create a private '${{ github.repository_owner }}/Match-Secrets' repository. Create a private 'Match-Secrets' repository manually and try again. If a private 'Match-Secrets' repository already exists, verify that the token permissions of the GH_PAT are set correctly (or update them) at https://github.com/settings/tokens and try again."
+            fi
+          # Otherwise, if a Match-Secrets repository exists, but it is public, cause validation to fail.
+          elif [[ "$visibility" == "public" ]]; then
+            failed=true
+            echo "::error::A '${{ github.repository_owner }}/Match-Secrets' repository was found, but it is public. Change the repository visibility to private (or delete it) and try again. If necessary, a private repository will be created for you."
+          else
+            echo "Found a private '${{ github.repository_owner }}/Match-Secrets' repository to use."
+          fi
+          
+          # Exit unsuccessfully if secret validation failed.
+          if [ $failed ]; then
+            exit 2
+          fi
+  
+  validate-fastlane-secrets:
+    name: Fastlane
+    needs: [validate-access-token, validate-match-secrets]
+    runs-on: macos-13
+    env:
+      GH_PAT: ${{ secrets.GH_PAT }}
+      GH_TOKEN: ${{ secrets.GH_PAT }}
+      FASTLANE_ISSUER_ID: ${{ secrets.FASTLANE_ISSUER_ID }}
+      FASTLANE_KEY_ID: ${{ secrets.FASTLANE_KEY_ID }}
+      FASTLANE_KEY: ${{ secrets.FASTLANE_KEY }}
+      MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
+      TEAMID: ${{ secrets.TEAMID }}
     steps:
-      # Checks-out the repo
       - name: Checkout Repo
         uses: actions/checkout@v3
 
-      # Validates the repo secrets
-      - name: Validate Secrets
-        run: |
-          # Validate Secrets
-          echo Validating Repository Secrets...
+      # Install project dependencies
+      - name: Install Project Dependencies
+        run: bundle install
 
+      - name: Validate Fastlane Secrets
+        run: |
+          # Validate Fastlane Secrets
+          
           # Validate TEAMID
           if [ -z "$TEAMID" ]; then
             failed=true
-            echo "::error::TEAMID secret is unset or empty. Set it and try again."
+            echo "::error::The TEAMID secret is unset or empty. Set it and try again."
           elif [ ${#TEAMID} -ne 10 ]; then
             failed=true
-            echo "::error::TEAMID secret is set but has wrong length. Verify that it is set correctly and try again."
-          fi
-
-          # Validate GH_PAT
-          if [ -z "$GH_PAT" ]; then
+            echo "::error::The TEAMID secret is set but has wrong length. Verify that it is set correctly and try again."
+          elif ! [[ $TEAMID =~ ^[A-Z0-9]+$ ]]; then
             failed=true
-            echo "::error::GH_PAT secret is unset or empty. Set it and try again."
-          elif [ "$(gh api -H "Accept: application/vnd.github+json" /repos/${{ github.repository_owner }}/Match-Secrets | jq --raw-output '.permissions.push')" != "true" ]; then
+            echo "::error::The TEAMID secret is set but invalid. Verify that it is set correctly (only uppercase letters and numbers) and try again."
+          fi
+          
+          # Validate MATCH_PASSWORD
+          if [ -z "$MATCH_PASSWORD" ]; then
             failed=true
-            echo "::error::GH_PAT secret is set but invalid or lacking appropriate privileges on the ${{ github.repository_owner }}/Match-Secrets repository. Verify that it is set correctly and try again."
+            echo "::error::The MATCH_PASSWORD secret is unset or empty. Set it and try again."
           fi
-
+          
+          # Ensure that fastlane exit codes are handled when output is piped.
+          set -o pipefail
+          
           # Validate FASTLANE_ISSUER_ID, FASTLANE_KEY_ID, and FASTLANE_KEY
+          FASTLANE_KEY_ID_PATTERN='^[A-Z0-9]+$'
+          FASTLANE_ISSUER_ID_PATTERN='^\{?[A-F0-9a-f]{8}-[A-F0-9a-f]{4}-[A-F0-9a-f]{4}-[A-F0-9a-f]{4}-[A-F0-9a-f]{12}\}?$'
+          
           if [ -z "$FASTLANE_ISSUER_ID" ] || [ -z "$FASTLANE_KEY_ID" ] || [ -z "$FASTLANE_KEY" ]; then
             failed=true
             [ -z "$FASTLANE_ISSUER_ID" ] && echo "::error::The FASTLANE_ISSUER_ID secret is unset or empty. Set it and try again."
             [ -z "$FASTLANE_KEY_ID"    ] && echo "::error::The FASTLANE_KEY_ID secret is unset or empty. Set it and try again."
             [ -z "$FASTLANE_KEY"       ] && echo "::error::The FASTLANE_KEY secret is unset or empty. Set it and try again."
-          elif ! echo "$FASTLANE_KEY" | openssl pkcs8 -nocrypt >/dev/null; then
+          elif [ ${#FASTLANE_KEY_ID} -ne 10 ]; then
             failed=true
-            echo "::error::The FASTLANE_KEY secret is set but invalid. Verify that it is set correctly and try again."
-          elif ! fastlane validate_secrets; then
+            echo "::error::The FASTLANE_KEY_ID secret is set but has wrong length. Verify that you copied it correctly from the 'Keys' tab at https://appstoreconnect.apple.com/access/api and try again."
+          elif ! [[ $FASTLANE_KEY_ID =~ $FASTLANE_KEY_ID_PATTERN ]]; then
             failed=true
-            echo "::error::Unable to create a valid authorization token for the App Store Connect API.\
-            Verify that the FASTLANE_ISSUER_ID, FASTLANE_KEY_ID, and FASTLANE_KEY secrets are set correctly and try again."
-          fi
-
-          # Validate MATCH_PASSWORD
-          if [ -z "$MATCH_PASSWORD" ]; then
+            echo "::error::The FASTLANE_KEY_ID secret is set but invalid. Verify that you copied it correctly from the 'Keys' tab at https://appstoreconnect.apple.com/access/api and try again."
+          elif ! [[ $FASTLANE_ISSUER_ID =~ $FASTLANE_ISSUER_ID_PATTERN ]]; then
             failed=true
-            echo "::error::The MATCH_PASSWORD secret is unset or empty. Set it and try again."
+            echo "::error::The FASTLANE_ISSUER_ID secret is set but invalid. Verify that you copied it correctly from the 'Keys' tab at https://appstoreconnect.apple.com/access/api and try again."
+          elif ! echo "$FASTLANE_KEY" | openssl pkcs8 -nocrypt >/dev/null; then
+            failed=true
+            echo "::error::The FASTLANE_KEY secret is set but invalid. Verify that you copied it correctly from the API Key file (*.p8) you downloaded and try again."
+          elif ! bundle exec fastlane validate_secrets 2>&1 | tee fastlane.log; then
+            if grep -q "bad decrypt" fastlane.log; then
+              failed=true
+              echo "::error::Unable to decrypt the Match-Secrets repository using the MATCH_PASSWORD secret. Verify that it is set correctly and try again."
+            elif grep -q -e "required agreement" -e "license agreement" fastlane.log; then
+              failed=true
+              echo "::error::Unable to create a valid authorization token for the App Store Connect API. Verify that the latest developer program license agreement has been accepted at https://developer.apple.com/account (review and accept any updated agreement), then wait a few minutes for changes to propagate and try again."
+            elif ! grep -q -e "No code signing identity found" -e "Could not install WWDR certificate" fastlane.log; then
+              failed=true
+              echo "::error::Unable to create a valid authorization token for the App Store Connect API. Verify that the FASTLANE_ISSUER_ID, FASTLANE_KEY_ID, and FASTLANE_KEY secrets are set correctly and try again."
+            fi
           fi
-
+          
           # Exit unsuccessfully if secret validation failed.
           if [ $failed ]; then
             exit 2
           fi
-        shell: bash
-        env:
-          TEAMID: ${{ secrets.TEAMID }}
-          GH_PAT: ${{ secrets.GH_PAT }}
-          FASTLANE_ISSUER_ID: ${{ secrets.FASTLANE_ISSUER_ID }}
-          FASTLANE_KEY_ID: ${{ secrets.FASTLANE_KEY_ID }}
-          FASTLANE_KEY: ${{ secrets.FASTLANE_KEY }}
-          MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
-          GH_TOKEN: ${{ secrets.GH_PAT }}

+ 1 - 0
Core_Data.xcdatamodeld/Core_Data.xcdatamodel/contents

@@ -66,6 +66,7 @@
         <attribute name="start" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
     </entity>
     <entity name="Meals" representedClassName="Meals" syncable="YES" codeGenerationType="class">
+        <attribute name="actualDate" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
         <attribute name="carbs" optional="YES" attributeType="Double" defaultValueString="0.0" usesScalarValueType="YES"/>
         <attribute name="createdAt" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
         <attribute name="date" optional="YES" attributeType="Date" usesScalarValueType="NO"/>

Разница между файлами не показана из-за своего большого размера
+ 15 - 15
Dependencies/OmniBLE/Localizations/de.lproj/Localizable.strings


Разница между файлами не показана из-за своего большого размера
+ 15 - 15
Dependencies/OmniBLE/Localizations/it.lproj/Localizable.strings


+ 1 - 1
Dependencies/OmniBLE/Localizations/nl.lproj/Localizable.strings

@@ -369,7 +369,7 @@
 "Suspend Delivery" = "Onderbreek toediening";
 
 /* Message for suspend duration selection action sheet */
-"Insulin delivery will be stopped until you resume manually. When would you like Loop to remind you to resume delivery?" = "De insulinetoediening wordt gestopt totdat u de toediening handmatig hervat. Wanneer wilt u dat Loop u eraan herinnert om de toediening te hervatten?";
+"Insulin delivery will be stopped until you resume manually. When would you like Loop to remind you to resume delivery?" = "De insulinetoediening wordt gestopt totdat je de toediening handmatig hervat. Wanneer wil je dat iAPS je eraan herinnert om de toediening te hervatten?";
 
 /* Button text for 30 minute suspend duration */
 "30 minutes" = "30 minuten";

Разница между файлами не показана из-за своего большого размера
+ 15 - 15
Dependencies/OmniBLE/Localizations/ru.lproj/Localizable.strings


+ 2 - 2
Dependencies/OmniBLE/Localizations/uk.lproj/Localizable.strings

@@ -816,7 +816,7 @@
 "Refresh Pump Manager Details" = "Оновити відомості про керування помпами";
 
 /* Section header for diagnostic section */
-"Diagnostics" = "Diagnostics";
+"Diagnostics" = "Діагностика";
 
 /* Text for read pod status navigation link */
-"Read Pod Status" = "Read Pod Status";
+"Read Pod Status" = "Отримати статус Pod'у";

+ 2 - 2
Dependencies/OmniKit/OmniKit/Resources/de.lproj/Localizable.strings

@@ -63,10 +63,10 @@
 "Communication issue: Unacknowledged command pending." = "Kommunikationsproblem: Unbestätigter Befehl steht noch aus.";
 
 /* Description for BeepPreference.manualCommands */
-"Confidence reminders will sound for commands you initiate, like bolus, cancel bolus, suspend, resume, save notification reminders, etc. When the app automatically adjusts delivery, no confidence reminders are used." = "Confidence reminders will sound for commands you initiate, like bolus, cancel bolus, suspend, resume, save notification reminders, etc. When the app automatically adjusts delivery, no confidence reminders are used.";
+"Confidence reminders will sound for commands you initiate, like bolus, cancel bolus, suspend, resume, save notification reminders, etc. When the app automatically adjusts delivery, no confidence reminders are used." = "Vertrauenserinnerungen ertönen für von Ihnen initiierte Befehle, wie Bolus, Bolus abbrechen, Unterbrechen, Fortsetzen, Benachrichtigungserinnerungen speichern usw. Wenn Loop die Abgabe automatisch anpasst, werden keine Vertrauenserinnerungen verwendet.";
 
 /* Description for BeepPreference.extended */
-"Confidence reminders will sound when the app automatically adjusts delivery as well as for commands you initiate." = "Confidence reminders will sound when the app automatically adjusts delivery as well as for commands you initiate.";
+"Confidence reminders will sound when the app automatically adjusts delivery as well as for commands you initiate." = "Vertrauenserinnerungen ertönen, wenn Loop die Lieferung automatisch anpasst, sowie für von Ihnen initiierte Befehle.";
 
 /* The title for AlarmCode.other notification */
 "Critical Pod Error" = "Kritischer Pod-Fehler";

+ 2 - 2
Dependencies/OmniKit/OmniKit/Resources/it.lproj/Localizable.strings

@@ -63,10 +63,10 @@
 "Communication issue: Unacknowledged command pending." = "Problema di comunicazione:  comando di conferma in attesa.";
 
 /* Description for BeepPreference.manualCommands */
-"Confidence reminders will sound for commands you initiate, like bolus, cancel bolus, suspend, resume, save notification reminders, etc. When the app automatically adjusts delivery, no confidence reminders are used." = "Confidence reminders will sound for commands you initiate, like bolus, cancel bolus, suspend, resume, save notification reminders, etc. When the app automatically adjusts delivery, no confidence reminders are used.";
+"Confidence reminders will sound for commands you initiate, like bolus, cancel bolus, suspend, resume, save notification reminders, etc. When the app automatically adjusts delivery, no confidence reminders are used." = "I promemoria di fiducia suoneranno per i comandi inoltrati, come boli, cancellazione boli, sospensioni, ripristini erogazione, salvataggi di promemoria, etc. Quando iAPS invece regolerà in automatico l'erogazione allora non userà alcun promemoria di fiducia.";
 
 /* Description for BeepPreference.extended */
-"Confidence reminders will sound when the app automatically adjusts delivery as well as for commands you initiate." = "Confidence reminders will sound when the app automatically adjusts delivery as well as for commands you initiate.";
+"Confidence reminders will sound when the app automatically adjusts delivery as well as for commands you initiate." = "I promemoria di fiducia suonano quando iAPS regola automaticamente l'erogazione e per i comandi avviati dall'utente.";
 
 /* The title for AlarmCode.other notification */
 "Critical Pod Error" = "Errore critico Pod";

Разница между файлами не показана из-за своего большого размера
+ 2 - 2
Dependencies/OmniKit/OmniKit/Resources/ru.lproj/Localizable.strings


Разница между файлами не показана из-за своего большого размера
+ 14 - 14
Dependencies/OmniKit/OmniKitUI/Resources/de.lproj/Localizable.strings


Разница между файлами не показана из-за своего большого размера
+ 14 - 14
Dependencies/OmniKit/OmniKitUI/Resources/it.lproj/Localizable.strings


+ 2 - 2
Dependencies/OmniKit/OmniKitUI/Resources/nl.lproj/Localizable.strings

@@ -297,10 +297,10 @@
 "If you cancel Pod setup, the current Pod will be deactivated and will be unusable." = "Als je de Podinstallatie annuleert, wordt de huidige Pod gedeactiveerd en onbruikbaar.";
 
 /* Instructions when deactivating pod that has been paired, but not attached. */
-"Incompletely set up pod must be deactivated before pairing with a new one. Please deactivate and discard pod." = "Een gedeeltelijk ingestelde pod moet eerst worden gedeactiveerd voordat er geprobeerd wordt een nieuwe pod te koppelen. Deactiveer pod en gooi weg.";
+"Incompletely set up pod must be deactivated before pairing with a new one. Please deactivate and discard pod." = "Een Pod die gedeeltelijk is ingesteld, moet eerst worden uitgeschakeld voordat je probeert een nieuwe pod te koppelen. Deactiveer de Pod uit en gooi hem weg.";
 
 /* Instructions when deactivating pod that has been paired and possibly attached. */
-"Incompletely set up pod must be deactivated before pairing with a new one. Please deactivate and remove pod." = "Een gedeeltelijk ingestelde pod moet eerst worden gedeactiveerd voordat er geprobeerd wordt een nieuwe pod te koppelen. Deactiveer pod en gooi weg.";
+"Incompletely set up pod must be deactivated before pairing with a new one. Please deactivate and remove pod." = "Een niet compleet ingestelde Pod, moet eerst worden uitgeschakeld voordat je probeert een nieuwe pod te koppelen. Deactiveer de Pod uit en gooi hem weg.";
 
 /* Button title to insert cannula during setup */
 "Insert Cannula" = "Canule inbrengen";

Разница между файлами не показана из-за своего большого размера
+ 14 - 14
Dependencies/OmniKit/OmniKitUI/Resources/ru.lproj/Localizable.strings


+ 2 - 2
Dependencies/OmniKit/OmniKitUI/Resources/uk.lproj/Localizable.strings

@@ -798,7 +798,7 @@ Silence Pod" = "Silence Pod";
 "Failed to update silence pod preference." = "Не вдалося оновити налаштування сигналів підтвердження.";
 
 /* Section header for diagnostic section */
-"Diagnostics" = "Diagnostics";
+"Diagnostics" = "Діагностика";
 
 /* Text for read pod status navigation link */
-"Read Pod Status" = "Read Pod Status";
+"Read Pod Status" = "Отримати статус Pod'у";

Разница между файлами не показана из-за своего большого размера
+ 1 - 1
FreeAPS/Resources/javascript/bundle/meal.js


+ 8 - 6
FreeAPS/Sources/APS/Storage/CarbsStorage.swift

@@ -69,7 +69,7 @@ final class BaseCarbsStorage: CarbsStorage, Injectable {
                 // Only use delay in first loop
                 var firstIndex = true
                 // New date for each carb equivalent
-                var useDate = entries.last?.createdAt ?? Date()
+                var useDate = entries.last?.actualDate ?? Date()
                 // Group and Identify all FPUs together
                 let fpuID = entries.last?.fpuID ?? ""
                 // Create an array of all future carb equivalents.
@@ -81,7 +81,8 @@ final class BaseCarbsStorage: CarbsStorage, Injectable {
                     } else { useDate = useDate.addingTimeInterval(interval.minutes.timeInterval) }
 
                     let eachCarbEntry = CarbsEntry(
-                        id: UUID().uuidString, createdAt: useDate, carbs: equivalent, fat: 0, protein: 0, note: nil,
+                        id: UUID().uuidString, createdAt: entries.last?.createdAt ?? Date(), actualDate: useDate,
+                        carbs: equivalent, fat: 0, protein: 0, note: nil,
                         enteredBy: CarbsEntry.manual, isFPU: true,
                         fpuID: fpuID
                     )
@@ -91,7 +92,7 @@ final class BaseCarbsStorage: CarbsStorage, Injectable {
                 // Save the array
                 if carbEquivalents > 0 {
                     self.storage.transaction { storage in
-                        storage.append(futureCarbArray, to: file, uniqBy: \.createdAt)
+                        storage.append(futureCarbArray, to: file, uniqBy: \.id)
                         uniqEvents = storage.retrieve(file, as: [CarbsEntry].self)?
                             .filter { $0.createdAt.addingTimeInterval(1.days.timeInterval) > Date() }
                             .sorted { $0.createdAt > $1.createdAt } ?? []
@@ -105,6 +106,7 @@ final class BaseCarbsStorage: CarbsStorage, Injectable {
                 let onlyCarbs = CarbsEntry(
                     id: entry.id ?? "",
                     createdAt: entry.createdAt,
+                    actualDate: entry.actualDate ?? entry.createdAt,
                     carbs: entry.carbs,
                     fat: nil,
                     protein: nil,
@@ -115,7 +117,7 @@ final class BaseCarbsStorage: CarbsStorage, Injectable {
                 )
 
                 self.storage.transaction { storage in
-                    storage.append(onlyCarbs, to: file, uniqBy: \.createdAt)
+                    storage.append(onlyCarbs, to: file, uniqBy: \.id)
                     uniqEvents = storage.retrieve(file, as: [CarbsEntry].self)?
                         .filter { $0.createdAt.addingTimeInterval(1.days.timeInterval) > Date() }
                         .sorted { $0.createdAt > $1.createdAt } ?? []
@@ -129,7 +131,7 @@ final class BaseCarbsStorage: CarbsStorage, Injectable {
             var carbDate = Date()
             if entries.isNotEmpty {
                 cbs = entries[0].carbs
-                carbDate = entries[0].createdAt
+                carbDate = entries[0].actualDate ?? entries[0].createdAt
             }
             if cbs != 0 {
                 self.coredataContext.perform {
@@ -197,7 +199,7 @@ final class BaseCarbsStorage: CarbsStorage, Injectable {
                 absolute: nil,
                 rate: nil,
                 eventType: .nsCarbCorrection,
-                createdAt: $0.createdAt,
+                createdAt: $0.actualDate ?? $0.createdAt,
                 enteredBy: CarbsEntry.manual,
                 bolus: nil,
                 insulin: nil,

+ 21 - 0
FreeAPS/Sources/Localizations/Main/ar.lproj/Localizable.strings

@@ -1371,6 +1371,12 @@ Enact a temp Basal or a temp target */
 "Delete Carb Equivalents?" = "Delete Carb Equivalents?";
 
 /* */
+"All FPUs of the meal will be deleted." = "All FPUs of the meal will be deleted.";
+
+/* */
+"Delete Glucose?" = "Delete Glucose?";
+
+/* */
 "Meal Presets" = "Meal Presets";
 
 /* */
@@ -1652,6 +1658,21 @@ Enact a temp Basal or a temp target */
 /* */
 "Hours X-Axis (6 default)" = "Hours X-Axis (6 default)";
 
+/* */
+"2 hours" = "2 hours";
+
+/* */
+"4 hours" = "4 hours";
+
+/* */
+"6 hours" = "6 hours";
+
+/* */
+"12 hours" = "12 hours";
+
+/* */
+"24 hours" = "24 hours";
+
 /* Average BG = */
 "Average" = "Average";
 

+ 23 - 2
FreeAPS/Sources/Localizations/Main/da.lproj/Localizable.strings

@@ -576,10 +576,10 @@ Enact a temp Basal or a temp target */
 "Temp Targets" = "Midlertidige Mål";
 
 /* Delete carbs from data table and Nightscout */
-"Delete Carbs?" = "Slet kulhydrater?";
+"Delete Carbs?" = "Delete Carbs?";
 
 /* Delete insulin from pump history and Nightscout */
-"Delete Insulin?" = "Slet insulin?";
+"Delete Insulin?" = "Delete Insulin?";
 
 /* Treatments list */
 "Treatments" = "Behandlinger";
@@ -1371,6 +1371,12 @@ Enact a temp Basal or a temp target */
 "Delete Carb Equivalents?" = "Delete Carb Equivalents?";
 
 /* */
+"All FPUs of the meal will be deleted." = "All FPUs of the meal will be deleted.";
+
+/* */
+"Delete Glucose?" = "Delete Glucose?";
+
+/* */
 "Meal Presets" = "Meal Presets";
 
 /* */
@@ -1652,6 +1658,21 @@ Enact a temp Basal or a temp target */
 /* */
 "Hours X-Axis (6 default)" = "Hours X-Axis (6 default)";
 
+/* */
+"2 hours" = "2 timer";
+
+/* */
+"4 hours" = "4 hours";
+
+/* */
+"6 hours" = "6 hours";
+
+/* */
+"12 hours" = "12 hours";
+
+/* */
+"24 hours" = "24 hours";
+
 /* Average BG = */
 "Average" = "Average";
 

+ 28 - 7
FreeAPS/Sources/Localizations/Main/de.lproj/Localizable.strings

@@ -576,10 +576,10 @@ Enact a temp Basal or a temp target */
 "Temp Targets" = "Temporäre Ziele";
 
 /* Delete carbs from data table and Nightscout */
-"Delete Carbs?" = "Kohlenhydrate löschen?";
+"Delete Carbs?" = "Delete Carbs?";
 
 /* Delete insulin from pump history and Nightscout */
-"Delete Insulin?" = "Insulin löschen?";
+"Delete Insulin?" = "Delete Insulin?";
 
 /* Treatments list */
 "Treatments" = "Behandlungen";
@@ -1368,7 +1368,13 @@ Enact a temp Basal or a temp target */
 "Statistics and Home View" = "Statistiken und Home-Ansicht";
 
 /* Alert text */
-"Delete Carb Equivalents?" = "Kohlenhydratäquivalente löschen?";
+"Delete Carb Equivalents?" = "Delete Carb Equivalents?";
+
+/* */
+"All FPUs of the meal will be deleted." = "All FPUs of the meal will be deleted.";
+
+/* */
+"Delete Glucose?" = "Delete Glucose?";
 
 /* */
 "Meal Presets" = "Mahlzeit Voreinstellungen";
@@ -1395,7 +1401,7 @@ Enact a temp Basal or a temp target */
 "Predictions" = "Prognosen";
 
 /* Watch Config Option */
-"Display Protein & Fat" = "Display Protein & Fat";
+"Display Protein & Fat" = "Zeige Eiweiß & Fett";
 
 /* ----------------------- New Bolus Calculator ---------------------------*/
 /* Warning about bolus recommendation. Title */
@@ -1480,7 +1486,7 @@ Enact a temp Basal or a temp target */
 /* Text for confidence reminders navigation link */
 "Confidence Reminders" = "Pumpe Bestätigungstöne";
 
-"Confidence reminders are beeps from the Pod which can be used to acknowledge selected commands when the Pod is not silenced." = "Confidence reminders are beeps from the Pod which can be used to acknowledge selected commands when the Pod is not silenced.";
+"Confidence reminders are beeps from the Pod which can be used to acknowledge selected commands when the Pod is not silenced." = "Vertrauenserinnerungen sind Pieptöne vom Pod, die verwendet werden können, um ausgewählte Befehle zu bestätigen.";
 
 /* button title for saving low reservoir reminder while saving */
 "Saving..." = "Speichern...";
@@ -1510,10 +1516,10 @@ Enact a temp Basal or a temp target */
 "No confidence reminders are used." = "Keine Erinnerungseinstellungen in Verwendung.";
 
 /* Description for BeepPreference.manualCommands */
-"Confidence reminders will sound for commands you initiate, like bolus, cancel bolus, suspend, resume, save notification reminders, etc. When the app automatically adjusts delivery, no confidence reminders are used." = "Confidence reminders will sound for commands you initiate, like bolus, cancel bolus, suspend, resume, save notification reminders, etc. When the app automatically adjusts delivery, no confidence reminders are used.";
+"Confidence reminders will sound for commands you initiate, like bolus, cancel bolus, suspend, resume, save notification reminders, etc. When the app automatically adjusts delivery, no confidence reminders are used." = "Vertrauenserinnerungen ertönen für von Ihnen initiierte Befehle, wie Bolus, Bolus abbrechen, Unterbrechen, Fortsetzen, Benachrichtigungserinnerungen speichern usw. Wenn Loop die Abgabe automatisch anpasst, werden keine Vertrauenserinnerungen verwendet.";
 
 /* Description for BeepPreference.extended */
-"Confidence reminders will sound when the app automatically adjusts delivery as well as for commands you initiate." = "Confidence reminders will sound when the app automatically adjusts delivery as well as for commands you initiate.";
+"Confidence reminders will sound when the app automatically adjusts delivery as well as for commands you initiate." = "Vertrauenserinnerungen ertönen, wenn Loop die Lieferung automatisch anpasst, sowie für von Ihnen initiierte Befehle.";
 
 /* Label text for expiration reminder default row */
 "Expiration Reminder Default" = "Standard Ablauf-Erinnerung";
@@ -1652,6 +1658,21 @@ Enact a temp Basal or a temp target */
 /* */
 "Hours X-Axis (6 default)" = "Stunden X-Achse (6 Standard)";
 
+/* */
+"2 hours" = "2 Stunden";
+
+/* */
+"4 hours" = "4 hours";
+
+/* */
+"6 hours" = "6 hours";
+
+/* */
+"12 hours" = "12 hours";
+
+/* */
+"24 hours" = "24 hours";
+
 /* Average BG = */
 "Average" = "Mittelwert";
 

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

@@ -576,7 +576,7 @@ Enact a temp Basal or a temp target */
 "Temp Targets" = "Temp Targets";
 
 /* Delete carbs from data table and Nightscout */
-"Delete Carbs?" = "¿Eliminar carbohidratos?";
+"Delete Carbs?" = "Delete Carbs?";
 
 /* Delete insulin from pump history and Nightscout */
 "Delete Insulin?" = "Delete Insulin?";
@@ -1371,6 +1371,12 @@ Enact a temp Basal or a temp target */
 "Delete Carb Equivalents?" = "Delete Carb Equivalents?";
 
 /* */
+"All FPUs of the meal will be deleted." = "All FPUs of the meal will be deleted.";
+
+/* */
+"Delete Glucose?" = "Delete Glucose?";
+
+/* */
 "Meal Presets" = "Meal Presets";
 
 /* */
@@ -1652,6 +1658,21 @@ Enact a temp Basal or a temp target */
 /* */
 "Hours X-Axis (6 default)" = "Hours X-Axis (6 default)";
 
+/* */
+"2 hours" = "2 hours";
+
+/* */
+"4 hours" = "4 hours";
+
+/* */
+"6 hours" = "6 hours";
+
+/* */
+"12 hours" = "12 hours";
+
+/* */
+"24 hours" = "24 hours";
+
 /* Average BG = */
 "Average" = "Average";
 

+ 21 - 0
FreeAPS/Sources/Localizations/Main/fi.lproj/Localizable.strings

@@ -1371,6 +1371,12 @@ Enact a temp Basal or a temp target */
 "Delete Carb Equivalents?" = "Delete Carb Equivalents?";
 
 /* */
+"All FPUs of the meal will be deleted." = "All FPUs of the meal will be deleted.";
+
+/* */
+"Delete Glucose?" = "Delete Glucose?";
+
+/* */
 "Meal Presets" = "Meal Presets";
 
 /* */
@@ -1652,6 +1658,21 @@ Enact a temp Basal or a temp target */
 /* */
 "Hours X-Axis (6 default)" = "Hours X-Axis (6 default)";
 
+/* */
+"2 hours" = "2 hours";
+
+/* */
+"4 hours" = "4 hours";
+
+/* */
+"6 hours" = "6 hours";
+
+/* */
+"12 hours" = "12 hours";
+
+/* */
+"24 hours" = "24 hours";
+
 /* Average BG = */
 "Average" = "Average";
 

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

@@ -576,7 +576,7 @@ Enact a temp Basal or a temp target */
 "Temp Targets" = "Cibles temporaires";
 
 /* Delete carbs from data table and Nightscout */
-"Delete Carbs?" = "Supprimer les glucides ?";
+"Delete Carbs?" = "Delete Carbs?";
 
 /* Delete insulin from pump history and Nightscout */
 "Delete Insulin?" = "Delete Insulin?";
@@ -1371,6 +1371,12 @@ Enact a temp Basal or a temp target */
 "Delete Carb Equivalents?" = "Delete Carb Equivalents?";
 
 /* */
+"All FPUs of the meal will be deleted." = "All FPUs of the meal will be deleted.";
+
+/* */
+"Delete Glucose?" = "Delete Glucose?";
+
+/* */
 "Meal Presets" = "Meal Presets";
 
 /* */
@@ -1652,6 +1658,21 @@ Enact a temp Basal or a temp target */
 /* */
 "Hours X-Axis (6 default)" = "Hours X-Axis (6 default)";
 
+/* */
+"2 hours" = "2 hours";
+
+/* */
+"4 hours" = "4 hours";
+
+/* */
+"6 hours" = "6 hours";
+
+/* */
+"12 hours" = "12 hours";
+
+/* */
+"24 hours" = "24 hours";
+
 /* Average BG = */
 "Average" = "Average";
 

+ 21 - 0
FreeAPS/Sources/Localizations/Main/he.lproj/Localizable.strings

@@ -1371,6 +1371,12 @@ Enact a temp Basal or a temp target */
 "Delete Carb Equivalents?" = "Delete Carb Equivalents?";
 
 /* */
+"All FPUs of the meal will be deleted." = "All FPUs of the meal will be deleted.";
+
+/* */
+"Delete Glucose?" = "Delete Glucose?";
+
+/* */
 "Meal Presets" = "Meal Presets";
 
 /* */
@@ -1652,6 +1658,21 @@ Enact a temp Basal or a temp target */
 /* */
 "Hours X-Axis (6 default)" = "Hours X-Axis (6 default)";
 
+/* */
+"2 hours" = "2 hours";
+
+/* */
+"4 hours" = "4 hours";
+
+/* */
+"6 hours" = "6 hours";
+
+/* */
+"12 hours" = "12 hours";
+
+/* */
+"24 hours" = "24 hours";
+
 /* Average BG = */
 "Average" = "Average";
 

+ 28 - 7
FreeAPS/Sources/Localizations/Main/it.lproj/Localizable.strings

@@ -576,10 +576,10 @@ Enact a temp Basal or a temp target */
 "Temp Targets" = "Obiettivi Temporanei";
 
 /* Delete carbs from data table and Nightscout */
-"Delete Carbs?" = "Cancella carboidrati?";
+"Delete Carbs?" = "Delete Carbs?";
 
 /* Delete insulin from pump history and Nightscout */
-"Delete Insulin?" = "Cancella l'insulina?";
+"Delete Insulin?" = "Delete Insulin?";
 
 /* Treatments list */
 "Treatments" = "Trattamenti";
@@ -1368,7 +1368,13 @@ Enact a temp Basal or a temp target */
 "Statistics and Home View" = "Statistiche e Vista Iniziale";
 
 /* Alert text */
-"Delete Carb Equivalents?" = "Cancella i carb equivalenti?";
+"Delete Carb Equivalents?" = "Delete Carb Equivalents?";
+
+/* */
+"All FPUs of the meal will be deleted." = "Tutti gli indici insulinici del pasto verranno eliminati.";
+
+/* */
+"Delete Glucose?" = "Cancella Glicemie?";
 
 /* */
 "Meal Presets" = "Pasto Predefinito";
@@ -1395,7 +1401,7 @@ Enact a temp Basal or a temp target */
 "Predictions" = "Previsione";
 
 /* Watch Config Option */
-"Display Protein & Fat" = "Display Protein & Fat";
+"Display Protein & Fat" = "Mostra Proteine & Grassi";
 
 /* ----------------------- New Bolus Calculator ---------------------------*/
 /* Warning about bolus recommendation. Title */
@@ -1480,7 +1486,7 @@ Enact a temp Basal or a temp target */
 /* Text for confidence reminders navigation link */
 "Confidence Reminders" = "Promemoria Di Sicurezza";
 
-"Confidence reminders are beeps from the Pod which can be used to acknowledge selected commands when the Pod is not silenced." = "Confidence reminders are beeps from the Pod which can be used to acknowledge selected commands when the Pod is not silenced.";
+"Confidence reminders are beeps from the Pod which can be used to acknowledge selected commands when the Pod is not silenced." = "I promemoria di fiducia sono segnali acustici emessi dal Pod che possono essere utilizzati per confermare i comandi selezionati quando il Pod non è silenziato.";
 
 /* button title for saving low reservoir reminder while saving */
 "Saving..." = "Sto salvando...";
@@ -1510,10 +1516,10 @@ Enact a temp Basal or a temp target */
 "No confidence reminders are used." = "Non vengono utilizzati promemoria di fiducia.";
 
 /* Description for BeepPreference.manualCommands */
-"Confidence reminders will sound for commands you initiate, like bolus, cancel bolus, suspend, resume, save notification reminders, etc. When the app automatically adjusts delivery, no confidence reminders are used." = "Confidence reminders will sound for commands you initiate, like bolus, cancel bolus, suspend, resume, save notification reminders, etc. When the app automatically adjusts delivery, no confidence reminders are used.";
+"Confidence reminders will sound for commands you initiate, like bolus, cancel bolus, suspend, resume, save notification reminders, etc. When the app automatically adjusts delivery, no confidence reminders are used." = "I promemoria di fiducia suoneranno per i comandi inoltrati, come boli, cancellazione boli, sospensioni, ripristini erogazione, salvataggi di promemoria, etc. Quando iAPS invece regolerà in automatico l'erogazione allora non userà alcun promemoria di fiducia.";
 
 /* Description for BeepPreference.extended */
-"Confidence reminders will sound when the app automatically adjusts delivery as well as for commands you initiate." = "Confidence reminders will sound when the app automatically adjusts delivery as well as for commands you initiate.";
+"Confidence reminders will sound when the app automatically adjusts delivery as well as for commands you initiate." = "I promemoria di fiducia suonano quando iAPS regola automaticamente l'erogazione e per i comandi avviati dall'utente.";
 
 /* Label text for expiration reminder default row */
 "Expiration Reminder Default" = "Promemoria scadenza predefinito";
@@ -1652,6 +1658,21 @@ Enact a temp Basal or a temp target */
 /* */
 "Hours X-Axis (6 default)" = "Ore X-Axis (6 predefinito)";
 
+/* */
+"2 hours" = "2 ore";
+
+/* */
+"4 hours" = "4 ore";
+
+/* */
+"6 hours" = "6 ore";
+
+/* */
+"12 hours" = "12 ore";
+
+/* */
+"24 hours" = "24 ore";
+
 /* Average BG = */
 "Average" = "Media";
 

+ 24 - 3
FreeAPS/Sources/Localizations/Main/nb.lproj/Localizable.strings

@@ -576,10 +576,10 @@ Enact a temp Basal or a temp target */
 "Temp Targets" = "Midlertidige mål";
 
 /* Delete carbs from data table and Nightscout */
-"Delete Carbs?" = "Slette karbohydrater?";
+"Delete Carbs?" = "Delete Carbs?";
 
 /* Delete insulin from pump history and Nightscout */
-"Delete Insulin?" = "Slette insulin?";
+"Delete Insulin?" = "Delete Insulin?";
 
 /* Treatments list */
 "Treatments" = "Behandlinger";
@@ -1368,7 +1368,13 @@ Enact a temp Basal or a temp target */
 "Statistics and Home View" = "Statistikk og startskjerm";
 
 /* Alert text */
-"Delete Carb Equivalents?" = "Slette karboekvivalenter?";
+"Delete Carb Equivalents?" = "Delete Carb Equivalents?";
+
+/* */
+"All FPUs of the meal will be deleted." = "All FPUs of the meal will be deleted.";
+
+/* */
+"Delete Glucose?" = "Delete Glucose?";
 
 /* */
 "Meal Presets" = "Forvalg av måltid";
@@ -1652,6 +1658,21 @@ Enact a temp Basal or a temp target */
 /* */
 "Hours X-Axis (6 default)" = "Timer å vise (6 er standard)";
 
+/* */
+"2 hours" = "2 timer";
+
+/* */
+"4 hours" = "4 hours";
+
+/* */
+"6 hours" = "6 hours";
+
+/* */
+"12 hours" = "12 hours";
+
+/* */
+"24 hours" = "24 hours";
+
 /* Average BG = */
 "Average" = "Gj.snitt";
 

Разница между файлами не показана из-за своего большого размера
+ 50 - 21
FreeAPS/Sources/Localizations/Main/nl.lproj/Localizable.strings


+ 21 - 0
FreeAPS/Sources/Localizations/Main/pl.lproj/Localizable.strings

@@ -1373,6 +1373,12 @@ Połączono z Nightscout!";
 "Delete Carb Equivalents?" = "Delete Carb Equivalents?";
 
 /* */
+"All FPUs of the meal will be deleted." = "All FPUs of the meal will be deleted.";
+
+/* */
+"Delete Glucose?" = "Delete Glucose?";
+
+/* */
 "Meal Presets" = "Meal Presets";
 
 /* */
@@ -1654,6 +1660,21 @@ Połączono z Nightscout!";
 /* */
 "Hours X-Axis (6 default)" = "Hours X-Axis (6 default)";
 
+/* */
+"2 hours" = "2 hours";
+
+/* */
+"4 hours" = "4 hours";
+
+/* */
+"6 hours" = "6 hours";
+
+/* */
+"12 hours" = "12 hours";
+
+/* */
+"24 hours" = "24 hours";
+
 /* Average BG = */
 "Average" = "Average";
 

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

@@ -576,7 +576,7 @@ Enact a temp Basal or a temp target */
 "Temp Targets" = "Alvos Temporários";
 
 /* Delete carbs from data table and Nightscout */
-"Delete Carbs?" = "Deletar carboidratos?";
+"Delete Carbs?" = "Delete Carbs?";
 
 /* Delete insulin from pump history and Nightscout */
 "Delete Insulin?" = "Delete Insulin?";
@@ -1371,6 +1371,12 @@ Enact a temp Basal or a temp target */
 "Delete Carb Equivalents?" = "Delete Carb Equivalents?";
 
 /* */
+"All FPUs of the meal will be deleted." = "All FPUs of the meal will be deleted.";
+
+/* */
+"Delete Glucose?" = "Delete Glucose?";
+
+/* */
 "Meal Presets" = "Meal Presets";
 
 /* */
@@ -1652,6 +1658,21 @@ Enact a temp Basal or a temp target */
 /* */
 "Hours X-Axis (6 default)" = "Hours X-Axis (6 default)";
 
+/* */
+"2 hours" = "2 hours";
+
+/* */
+"4 hours" = "4 hours";
+
+/* */
+"6 hours" = "6 hours";
+
+/* */
+"12 hours" = "12 hours";
+
+/* */
+"24 hours" = "24 hours";
+
 /* Average BG = */
 "Average" = "Average";
 

+ 21 - 0
FreeAPS/Sources/Localizations/Main/pt-PT.lproj/Localizable.strings

@@ -1371,6 +1371,12 @@ Enact a temp Basal or a temp target */
 "Delete Carb Equivalents?" = "Delete Carb Equivalents?";
 
 /* */
+"All FPUs of the meal will be deleted." = "All FPUs of the meal will be deleted.";
+
+/* */
+"Delete Glucose?" = "Delete Glucose?";
+
+/* */
 "Meal Presets" = "Meal Presets";
 
 /* */
@@ -1652,6 +1658,21 @@ Enact a temp Basal or a temp target */
 /* */
 "Hours X-Axis (6 default)" = "Hours X-Axis (6 default)";
 
+/* */
+"2 hours" = "2 hours";
+
+/* */
+"4 hours" = "4 hours";
+
+/* */
+"6 hours" = "6 hours";
+
+/* */
+"12 hours" = "12 hours";
+
+/* */
+"24 hours" = "24 hours";
+
 /* Average BG = */
 "Average" = "Average";
 

Разница между файлами не показана из-за своего большого размера
+ 27 - 6
FreeAPS/Sources/Localizations/Main/ru.lproj/Localizable.strings


+ 21 - 0
FreeAPS/Sources/Localizations/Main/sk.lproj/Localizable.strings

@@ -1371,6 +1371,12 @@ Enact a temp Basal or a temp target */
 "Delete Carb Equivalents?" = "Delete Carb Equivalents?";
 
 /* */
+"All FPUs of the meal will be deleted." = "All FPUs of the meal will be deleted.";
+
+/* */
+"Delete Glucose?" = "Delete Glucose?";
+
+/* */
 "Meal Presets" = "Meal Presets";
 
 /* */
@@ -1652,6 +1658,21 @@ Enact a temp Basal or a temp target */
 /* */
 "Hours X-Axis (6 default)" = "Hours X-Axis (6 default)";
 
+/* */
+"2 hours" = "2 hours";
+
+/* */
+"4 hours" = "4 hours";
+
+/* */
+"6 hours" = "6 hours";
+
+/* */
+"12 hours" = "12 hours";
+
+/* */
+"24 hours" = "24 hours";
+
 /* Average BG = */
 "Average" = "Average";
 

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

@@ -1368,7 +1368,13 @@ Enact a temp Basal or a temp target */
 "Statistics and Home View" = "Statistik och Diagram";
 
 /* Alert text */
-"Delete Carb Equivalents?" = "Radera dessa poster?";
+"Delete Carb Equivalents?" = "Ta bort dessa fett/protein-poster?";
+
+/* */
+"All FPUs of the meal will be deleted." = "Alla poster fett/protein-poster tillhörande denna måltid kommer att tas bort.";
+
+/* */
+"Delete Glucose?" = "Ta bort blodsockervärde?";
 
 /* */
 "Meal Presets" = "Förval";

+ 23 - 2
FreeAPS/Sources/Localizations/Main/tr.lproj/Localizable.strings

@@ -576,10 +576,10 @@ Enact a temp Basal or a temp target */
 "Temp Targets" = "Geçici Hedefler";
 
 /* Delete carbs from data table and Nightscout */
-"Delete Carbs?" = "Karbonhidratları sil?";
+"Delete Carbs?" = "Delete Carbs?";
 
 /* Delete insulin from pump history and Nightscout */
-"Delete Insulin?" = "İnsülin silinsin mi?";
+"Delete Insulin?" = "Delete Insulin?";
 
 /* Treatments list */
 "Treatments" = "Tedaviler";
@@ -1371,6 +1371,12 @@ Enact a temp Basal or a temp target */
 "Delete Carb Equivalents?" = "Delete Carb Equivalents?";
 
 /* */
+"All FPUs of the meal will be deleted." = "All FPUs of the meal will be deleted.";
+
+/* */
+"Delete Glucose?" = "Delete Glucose?";
+
+/* */
 "Meal Presets" = "Meal Presets";
 
 /* */
@@ -1652,6 +1658,21 @@ Enact a temp Basal or a temp target */
 /* */
 "Hours X-Axis (6 default)" = "Hours X-Axis (6 default)";
 
+/* */
+"2 hours" = "2 saat";
+
+/* */
+"4 hours" = "4 hours";
+
+/* */
+"6 hours" = "6 hours";
+
+/* */
+"12 hours" = "12 hours";
+
+/* */
+"24 hours" = "24 hours";
+
 /* Average BG = */
 "Average" = "Ortalama";
 

+ 21 - 0
FreeAPS/Sources/Localizations/Main/uk.lproj/Localizable.strings

@@ -1371,6 +1371,12 @@ Enact a temp Basal or a temp target */
 "Delete Carb Equivalents?" = "Видалити вуглеводні еквіваленти?";
 
 /* */
+"All FPUs of the meal will be deleted." = "Усі FPU страви буде видалено.";
+
+/* */
+"Delete Glucose?" = "Видалити глюкозу?";
+
+/* */
 "Meal Presets" = "Попередні Налаштування Їжі";
 
 /* */
@@ -1652,6 +1658,21 @@ Enact a temp Basal or a temp target */
 /* */
 "Hours X-Axis (6 default)" = "Вісь Х годин (6 за замовчуванням)";
 
+/* */
+"2 hours" = "2 години";
+
+/* */
+"4 hours" = "4 години";
+
+/* */
+"6 hours" = "6 годин";
+
+/* */
+"12 hours" = "12 годин";
+
+/* */
+"24 hours" = "24 години";
+
 /* Average BG = */
 "Average" = "Середній";
 

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

@@ -576,7 +576,7 @@ Enact a temp Basal or a temp target */
 "Temp Targets" = "临时目标";
 
 /* Delete carbs from data table and Nightscout */
-"Delete Carbs?" = "删除碳水?";
+"Delete Carbs?" = "Delete Carbs?";
 
 /* Delete insulin from pump history and Nightscout */
 "Delete Insulin?" = "Delete Insulin?";
@@ -1371,6 +1371,12 @@ Enact a temp Basal or a temp target */
 "Delete Carb Equivalents?" = "Delete Carb Equivalents?";
 
 /* */
+"All FPUs of the meal will be deleted." = "All FPUs of the meal will be deleted.";
+
+/* */
+"Delete Glucose?" = "Delete Glucose?";
+
+/* */
 "Meal Presets" = "Meal Presets";
 
 /* */
@@ -1652,6 +1658,21 @@ Enact a temp Basal or a temp target */
 /* */
 "Hours X-Axis (6 default)" = "Hours X-Axis (6 default)";
 
+/* */
+"2 hours" = "2 hours";
+
+/* */
+"4 hours" = "4 hours";
+
+/* */
+"6 hours" = "6 hours";
+
+/* */
+"12 hours" = "12 hours";
+
+/* */
+"24 hours" = "24 hours";
+
 /* Average BG = */
 "Average" = "Average";
 

+ 2 - 0
FreeAPS/Sources/Models/CarbsEntry.swift

@@ -3,6 +3,7 @@ import Foundation
 struct CarbsEntry: JSON, Equatable, Hashable {
     let id: String?
     let createdAt: Date
+    let actualDate: Date?
     let carbs: Decimal
     let fat: Decimal?
     let protein: Decimal?
@@ -27,6 +28,7 @@ extension CarbsEntry {
     private enum CodingKeys: String, CodingKey {
         case id = "_id"
         case createdAt = "created_at"
+        case actualDate
         case carbs
         case fat
         case protein

+ 6 - 2
FreeAPS/Sources/Modules/AddCarbs/AddCarbsStateModel.swift

@@ -22,6 +22,8 @@ extension AddCarbs {
         @Published var summary: String = ""
         @Published var skipBolus: Bool = false
 
+        let now = Date.now
+
         let coredataContext = CoreDataStack.shared.persistentContainer.viewContext
 
         override func subscribe() {
@@ -41,7 +43,8 @@ extension AddCarbs {
 
             let carbsToStore = [CarbsEntry(
                 id: id_,
-                createdAt: Date.now,
+                createdAt: now,
+                actualDate: date,
                 carbs: carbs,
                 fat: fat,
                 protein: protein,
@@ -191,7 +194,8 @@ extension AddCarbs {
             coredataContext.performAndWait {
                 let save = Meals(context: coredataContext)
                 if let entry = stored.first {
-                    save.createdAt = Date.now
+                    save.createdAt = now
+                    save.actualDate = entry.actualDate ?? Date.now
                     save.id = entry.id ?? ""
                     save.fpuID = entry.fpuID ?? ""
                     save.carbs = Double(entry.carbs)

+ 2 - 5
FreeAPS/Sources/Modules/AddCarbs/View/AddCarbsRootView.swift

@@ -82,19 +82,16 @@ extension AddCarbs {
                                 pushed = true
                             } label: { Text("Now") }.buttonStyle(.borderless).foregroundColor(.secondary).padding(.trailing, 5)
                         } else {
-                            Button { state.date = state.date.addingTimeInterval(-10.minutes.timeInterval) }
+                            Button { state.date = state.date.addingTimeInterval(-15.minutes.timeInterval) }
                             label: { Image(systemName: "minus.circle") }.tint(.blue).buttonStyle(.borderless)
                             DatePicker(
                                 "Time",
                                 selection: $state.date,
-                                in: ...now,
                                 displayedComponents: [.hourAndMinute]
                             ).controlSize(.mini)
                                 .labelsHidden()
                             Button {
-                                if state.date.addingTimeInterval(5.minutes.timeInterval) < now {
-                                    state.date = state.date.addingTimeInterval(10.minutes.timeInterval)
-                                }
+                                state.date = state.date.addingTimeInterval(15.minutes.timeInterval)
                             }
                             label: { Image(systemName: "plus.circle") }.tint(.blue).buttonStyle(.borderless)
                         }

+ 10 - 2
FreeAPS/Sources/Modules/Bolus/BolusStateModel.swift

@@ -281,17 +281,25 @@ extension Bolus {
                 return
             }
 
+            var date = Date()
+
+            if let mealDate = meals.actualDate {
+                date = mealDate
+            } else if let mealdate = meals.createdAt {
+                date = mealdate
+            }
+
             let mealArray = DataTable.Treatment(
                 units: units,
                 type: .carbs,
-                date: meals.createdAt ?? Date(),
+                date: date,
                 id: meals.id ?? "",
                 isFPU: deleteTwice ? true : false,
                 fpuID: deleteTwice ? (meals.fpuID ?? "") : ""
             )
 
             print(
-                "meals 2: ID: " + (mealArray.id ?? "").description + " FPU ID: " + (mealArray.fpuID ?? "")
+                "meals 2: ID: " + mealArray.id.description + " FPU ID: " + (mealArray.fpuID ?? "")
                     .description
             )
 

+ 10 - 4
FreeAPS/Sources/Modules/DataTable/DataTableStateModel.swift

@@ -36,7 +36,7 @@ extension DataTable {
         private func setupTreatments() {
             DispatchQueue.global().async {
                 let units = self.settingsManager.settings.units
-
+                var date = Date.now
                 let carbs = self.provider.carbs()
                     .filter { !($0.isFPU ?? false) }
                     .map {
@@ -44,14 +44,20 @@ extension DataTable {
                             return Treatment(
                                 units: units,
                                 type: .carbs,
-                                date: $0.createdAt,
+                                date: $0.actualDate ?? $0.createdAt,
                                 amount: $0.carbs,
                                 id: id,
                                 fpuID: $0.fpuID,
                                 note: $0.note
                             )
                         } else {
-                            return Treatment(units: units, type: .carbs, date: $0.createdAt, amount: $0.carbs, note: $0.note)
+                            return Treatment(
+                                units: units,
+                                type: .carbs,
+                                date: $0.actualDate ?? $0.createdAt,
+                                amount: $0.carbs,
+                                note: $0.note
+                            )
                         }
                     }
 
@@ -61,7 +67,7 @@ extension DataTable {
                         Treatment(
                             units: units,
                             type: .fpus,
-                            date: $0.createdAt,
+                            date: $0.actualDate ?? $0.createdAt,
                             amount: $0.carbs,
                             id: $0.id,
                             isFPU: $0.isFPU,

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

@@ -219,15 +219,6 @@ extension Home {
             }
         }
 
-        func saveSettings() {
-            coredataContext.perform {
-                let settings = UXSettings(context: self.coredataContext)
-                settings.hours = self.hours
-                settings.date = Date.now
-                try? self.coredataContext.save()
-            }
-        }
-
         private func setupGlucose() {
             DispatchQueue.main.async { [weak self] in
                 guard let self = self else { return }

+ 10 - 2
FreeAPS/Sources/Modules/Home/View/Chart/MainChartView.swift

@@ -738,7 +738,11 @@ extension MainChartView {
         calculationQueue.async {
             let realCarbs = carbs.filter { !($0.isFPU ?? false) }
             let dots = realCarbs.map { value -> DotInfo in
-                let center = timeToInterpolatedPoint(value.createdAt.timeIntervalSince1970, fullSize: fullSize)
+                let center = timeToInterpolatedPoint(
+                    value.actualDate != nil ? (value.actualDate ?? Date()).timeIntervalSince1970 : value.createdAt
+                        .timeIntervalSince1970,
+                    fullSize: fullSize
+                )
                 let size = Config.carbsSize + CGFloat(value.carbs) * Config.carbsScale
                 let rect = CGRect(x: center.x - size / 2, y: center.y - size / 2, width: size, height: size)
                 return DotInfo(rect: rect, value: value.carbs)
@@ -761,7 +765,11 @@ extension MainChartView {
         calculationQueue.async {
             let fpus = carbs.filter { $0.isFPU ?? false }
             let dots = fpus.map { value -> DotInfo in
-                let center = timeToInterpolatedPoint(value.createdAt.timeIntervalSince1970, fullSize: fullSize)
+                let center = timeToInterpolatedPoint(
+                    value.actualDate != nil ? (value.actualDate ?? Date()).timeIntervalSince1970 : value.createdAt
+                        .timeIntervalSince1970,
+                    fullSize: fullSize
+                )
                 let size = Config.fpuSize + CGFloat(value.carbs) * Config.fpuScale
                 let rect = CGRect(x: center.x - size / 2, y: center.y - size / 2, width: size, height: size)
                 return DotInfo(rect: rect, value: value.carbs)

+ 11 - 34
FreeAPS/Sources/Modules/Home/View/HomeRootView.swift

@@ -55,11 +55,6 @@ extension Home {
             sortDescriptors: [NSSortDescriptor(key: "date", ascending: false)]
         ) var enactedSliderTT: FetchedResults<TempTargetsSlider>
 
-        @FetchRequest(
-            entity: UXSettings.entity(),
-            sortDescriptors: [NSSortDescriptor(key: "date", ascending: false)]
-        ) var fetchedSettings: FetchedResults<UXSettings>
-
         private var numberFormatter: NumberFormatter {
             let formatter = NumberFormatter()
             formatter.numberStyle = .decimal
@@ -425,15 +420,11 @@ extension Home {
         }
 
         var timeInterval: some View {
-            HStack(alignment: .center) {
-                let saveButton = UXSettings(context: moc)
+            HStack {
                 ForEach(timeButtons) { button in
                     Text(button.active ? NSLocalizedString(button.label, comment: "") : button.number).onTapGesture {
                         let index = timeButtons.firstIndex(where: { $0.label == button.label }) ?? 0
-                        highlightButtons(index, onAppear: false)
-                        saveButton.hours = button.hours
-                        saveButton.date = Date.now
-                        try? moc.save()
+                        highlightButtons(index)
                         state.hours = button.hours
                     }
                     .foregroundStyle(button.active ? (colorScheme == .dark ? Color.white : Color.black).opacity(0.9) : .secondary)
@@ -521,29 +512,16 @@ extension Home {
             return (name: profileString, isOn: display)
         }
 
-        func highlightButtons(_ int: Int?, onAppear: Bool) {
+        func highlightButtons(_ int: Int) {
             var index = 0
-            if let integer = int, !onAppear {
-                repeat {
-                    if index == integer {
-                        timeButtons[index].active = true
-                    } else {
-                        timeButtons[index].active = false
-                    }
-                    index += 1
-                } while index < timeButtons.count
-            } else if onAppear {
-                let i = timeButtons.firstIndex(where: { $0.hours == (fetchedSettings.first?.hours ?? 6) }) ?? 2
-                index = 0
-                repeat {
-                    if index == i {
-                        timeButtons[index].active = true
-                    } else {
-                        timeButtons[index].active = false
-                    }
-                    index += 1
-                } while index < timeButtons.count
-            }
+            repeat {
+                if index == int {
+                    timeButtons[index].active = true
+                } else {
+                    timeButtons[index].active = false
+                }
+                index += 1
+            } while index < timeButtons.count
         }
 
         @ViewBuilder private func bottomPanel(_: GeometryProxy) -> some View {
@@ -736,7 +714,6 @@ extension Home {
             }
             .onAppear(perform: configureView)
             .navigationTitle("Home")
-
             .navigationBarHidden(true)
             .ignoresSafeArea(.keyboard)
             .popup(isPresented: isStatusPopupPresented, alignment: .top, direction: .top) {

+ 4 - 0
FreeAPS/Sources/Modules/NightscoutConfig/NightscoutConfigStateModel.swift

@@ -61,6 +61,10 @@ extension NightscoutConfig {
         }
 
         func connect() {
+            if let CheckURL = url.last, CheckURL == "/" {
+                let fixedURL = url.dropLast()
+                url = String(fixedURL)
+            }
             guard let url = URL(string: url) else {
                 message = "Invalid URL"
                 return

+ 3 - 3
FreeAPS/Sources/Services/HealthKit/HealthKitManager.swift

@@ -195,13 +195,13 @@ final class BaseHealthKitManager: HealthKitManager, Injectable, CarbsObserver, P
             let sampleDates = samples.map(\.startDate)
             let samplesToSave = carbsWithId
                 .filter { !sampleIDs.contains($0.id ?? "") } // id existing in AH
-                .filter { !sampleDates.contains($0.createdAt) } // not id but exaclty the same datetime
+                .filter { !sampleDates.contains($0.actualDate ?? $0.createdAt) } // not id but exaclty the same datetime
                 .map {
                     HKQuantitySample(
                         type: sampleType,
                         quantity: HKQuantity(unit: .gram(), doubleValue: Double($0.carbs)),
-                        start: $0.createdAt,
-                        end: $0.createdAt,
+                        start: $0.actualDate ?? $0.createdAt,
+                        end: $0.actualDate ?? $0.createdAt,
                         metadata: [
                             HKMetadataKeySyncIdentifier: $0.id ?? "_id",
                             HKMetadataKeySyncVersion: 1,

+ 1 - 1
FreeAPS/Sources/Services/Network/NightscoutAPI.swift

@@ -148,7 +148,7 @@ extension NightscoutAPI {
         components.port = url.port
         components.path = Config.treatmentsPath
 
-        var arguments = "find[_id][$eq]"
+        var arguments = "find[id][$eq]"
         if treatement.isFPU ?? false {
             arguments = "find[fpuID][$eq]"
         }

+ 1 - 0
FreeAPS/Sources/Services/WatchManager/WatchManager.swift

@@ -332,6 +332,7 @@ extension BaseWatchManager: WCSessionDelegate {
                 [CarbsEntry(
                     id: UUID().uuidString,
                     createdAt: Date(),
+                    actualDate: nil,
                     carbs: Decimal(carbs),
                     fat: Decimal(fat),
                     protein: Decimal(protein), note: nil,

+ 1 - 0
FreeAPS/Sources/Shortcuts/Carbs/CarbPresetIntentRequest.swift

@@ -13,6 +13,7 @@ import Foundation
             [CarbsEntry(
                 id: UUID().uuidString,
                 createdAt: dateAdded,
+                actualDate: dateAdded,
                 carbs: carbs,
                 fat: Decimal(quantityFat),
                 protein: Decimal(quantityProtein),

+ 72 - 68
Gemfile.lock

@@ -1,52 +1,53 @@
 GEM
   remote: https://rubygems.org/
   specs:
-    CFPropertyList (3.0.4)
+    CFPropertyList (3.0.6)
       rexml
-    addressable (2.8.0)
-      public_suffix (>= 2.0.2, < 5.0)
+    addressable (2.8.5)
+      public_suffix (>= 2.0.2, < 6.0)
     artifactory (3.0.15)
     atomos (0.1.3)
     aws-eventstream (1.2.0)
-    aws-partitions (1.516.0)
-    aws-sdk-core (3.121.2)
+    aws-partitions (1.824.0)
+    aws-sdk-core (3.181.1)
       aws-eventstream (~> 1, >= 1.0.2)
-      aws-partitions (~> 1, >= 1.239.0)
+      aws-partitions (~> 1, >= 1.651.0)
+      aws-sigv4 (~> 1.5)
+      jmespath (~> 1, >= 1.6.1)
+    aws-sdk-kms (1.71.0)
+      aws-sdk-core (~> 3, >= 3.177.0)
       aws-sigv4 (~> 1.1)
-      jmespath (~> 1.0)
-    aws-sdk-kms (1.50.0)
-      aws-sdk-core (~> 3, >= 3.121.2)
-      aws-sigv4 (~> 1.1)
-    aws-sdk-s3 (1.104.0)
-      aws-sdk-core (~> 3, >= 3.121.2)
+    aws-sdk-s3 (1.134.0)
+      aws-sdk-core (~> 3, >= 3.181.0)
       aws-sdk-kms (~> 1)
-      aws-sigv4 (~> 1.4)
-    aws-sigv4 (1.4.0)
+      aws-sigv4 (~> 1.6)
+    aws-sigv4 (1.6.0)
       aws-eventstream (~> 1, >= 1.0.2)
     babosa (1.0.4)
-    claide (1.0.3)
+    claide (1.1.0)
     colored (1.2)
     colored2 (3.1.2)
     commander (4.6.0)
       highline (~> 2.0.0)
     declarative (0.0.20)
-    digest-crc (0.6.4)
+    digest-crc (0.6.5)
       rake (>= 12.0.0, < 14.0.0)
     domain_name (0.5.20190701)
       unf (>= 0.0.5, < 1.0.0)
-    dotenv (2.7.6)
+    dotenv (2.8.1)
     emoji_regex (3.2.3)
-    excon (0.87.0)
-    faraday (1.8.0)
+    excon (0.103.0)
+    faraday (1.10.3)
       faraday-em_http (~> 1.0)
       faraday-em_synchrony (~> 1.0)
       faraday-excon (~> 1.1)
-      faraday-httpclient (~> 1.0.1)
+      faraday-httpclient (~> 1.0)
+      faraday-multipart (~> 1.0)
       faraday-net_http (~> 1.0)
-      faraday-net_http_persistent (~> 1.1)
+      faraday-net_http_persistent (~> 1.0)
       faraday-patron (~> 1.0)
       faraday-rack (~> 1.0)
-      multipart-post (>= 1.2, < 3)
+      faraday-retry (~> 1.0)
       ruby2_keywords (>= 0.0.4)
     faraday-cookie_jar (0.0.7)
       faraday (>= 0.8.0)
@@ -55,14 +56,17 @@ GEM
     faraday-em_synchrony (1.0.0)
     faraday-excon (1.1.0)
     faraday-httpclient (1.0.1)
+    faraday-multipart (1.0.4)
+      multipart-post (~> 2)
     faraday-net_http (1.0.1)
     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.0)
       faraday (~> 1.0)
-    fastimage (2.2.5)
-    fastlane (2.196.0)
+    fastimage (2.2.7)
+    fastlane (2.215.0)
       CFPropertyList (>= 2.3, < 4.0.0)
       addressable (>= 2.8, < 3.0.0)
       artifactory (~> 3.0)
@@ -83,10 +87,11 @@ GEM
       google-apis-playcustomapp_v1 (~> 0.1)
       google-cloud-storage (~> 1.31)
       highline (~> 2.0)
+      http-cookie (~> 1.0.5)
       json (< 3.0.0)
       jwt (>= 2.1.0, < 3)
       mini_magick (>= 4.9.4, < 5.0.0)
-      multipart-post (~> 2.0.0)
+      multipart-post (>= 2.0.0, < 3.0.0)
       naturally (~> 2.2)
       optparse (~> 0.1.1)
       plist (>= 3.1.0, < 4.0.0)
@@ -94,7 +99,7 @@ GEM
       security (= 0.1.3)
       simctl (~> 1.6.3)
       terminal-notifier (>= 2.0.0, < 3.0.0)
-      terminal-table (>= 1.4.5, < 2.0.0)
+      terminal-table (~> 3)
       tty-screen (>= 0.6.3, < 1.0.0)
       tty-spinner (>= 0.8.0, < 1.0.0)
       word_wrap (~> 1.0.0)
@@ -102,9 +107,9 @@ GEM
       xcpretty (~> 0.3.0)
       xcpretty-travis-formatter (>= 0.0.3)
     gh_inspector (1.1.3)
-    google-apis-androidpublisher_v3 (0.12.0)
-      google-apis-core (>= 0.4, < 2.a)
-    google-apis-core (0.4.1)
+    google-apis-androidpublisher_v3 (0.49.0)
+      google-apis-core (>= 0.11.0, < 2.a)
+    google-apis-core (0.11.1)
       addressable (~> 2.5, >= 2.5.1)
       googleauth (>= 0.16.2, < 2.a)
       httpclient (>= 2.8.1, < 3.a)
@@ -113,74 +118,72 @@ GEM
       retriable (>= 2.0, < 4.a)
       rexml
       webrick
-    google-apis-iamcredentials_v1 (0.7.0)
-      google-apis-core (>= 0.4, < 2.a)
-    google-apis-playcustomapp_v1 (0.5.0)
-      google-apis-core (>= 0.4, < 2.a)
-    google-apis-storage_v1 (0.8.0)
-      google-apis-core (>= 0.4, < 2.a)
+    google-apis-iamcredentials_v1 (0.17.0)
+      google-apis-core (>= 0.11.0, < 2.a)
+    google-apis-playcustomapp_v1 (0.13.0)
+      google-apis-core (>= 0.11.0, < 2.a)
+    google-apis-storage_v1 (0.19.0)
+      google-apis-core (>= 0.9.0, < 2.a)
     google-cloud-core (1.6.0)
       google-cloud-env (~> 1.0)
       google-cloud-errors (~> 1.0)
-    google-cloud-env (1.5.0)
-      faraday (>= 0.17.3, < 2.0)
-    google-cloud-errors (1.2.0)
-    google-cloud-storage (1.34.1)
-      addressable (~> 2.5)
+    google-cloud-env (1.6.0)
+      faraday (>= 0.17.3, < 3.0)
+    google-cloud-errors (1.3.1)
+    google-cloud-storage (1.44.0)
+      addressable (~> 2.8)
       digest-crc (~> 0.4)
       google-apis-iamcredentials_v1 (~> 0.1)
-      google-apis-storage_v1 (~> 0.1)
+      google-apis-storage_v1 (~> 0.19.0)
       google-cloud-core (~> 1.6)
       googleauth (>= 0.16.2, < 2.a)
       mini_mime (~> 1.0)
-    googleauth (1.0.0)
-      faraday (>= 0.17.3, < 2.0)
+    googleauth (1.8.0)
+      faraday (>= 0.17.3, < 3.a)
       jwt (>= 1.4, < 3.0)
-      memoist (~> 0.16)
       multi_json (~> 1.11)
       os (>= 0.9, < 2.0)
       signet (>= 0.16, < 2.a)
     highline (2.0.3)
-    http-cookie (1.0.4)
+    http-cookie (1.0.5)
       domain_name (~> 0.5)
     httpclient (2.8.3)
-    jmespath (1.4.0)
-    json (2.6.0)
-    jwt (2.3.0)
-    memoist (0.16.2)
-    mini_magick (4.11.0)
-    mini_mime (1.1.2)
+    jmespath (1.6.2)
+    json (2.6.3)
+    jwt (2.7.1)
+    mini_magick (4.12.0)
+    mini_mime (1.1.5)
     multi_json (1.15.0)
-    multipart-post (2.0.0)
+    multipart-post (2.3.0)
     nanaimo (0.3.0)
     naturally (2.2.1)
     optparse (0.1.1)
-    os (1.1.1)
-    plist (3.6.0)
-    public_suffix (4.0.6)
+    os (1.1.4)
+    plist (3.7.0)
+    public_suffix (5.0.3)
     rake (13.0.6)
-    representable (3.1.1)
+    representable (3.2.0)
       declarative (< 0.1.0)
       trailblazer-option (>= 0.1.1, < 0.2.0)
       uber (< 0.2.0)
     retriable (3.1.2)
-    rexml (3.2.5)
+    rexml (3.2.6)
     rouge (2.0.7)
     ruby2_keywords (0.0.5)
     rubyzip (2.3.2)
     security (0.1.3)
-    signet (0.16.0)
+    signet (0.18.0)
       addressable (~> 2.8)
-      faraday (>= 0.17.3, < 2.0)
+      faraday (>= 0.17.5, < 3.a)
       jwt (>= 1.5, < 3.0)
       multi_json (~> 1.10)
-    simctl (1.6.8)
+    simctl (1.6.10)
       CFPropertyList
       naturally
     terminal-notifier (2.0.0)
-    terminal-table (1.8.0)
-      unicode-display_width (~> 1.1, >= 1.1.1)
-    trailblazer-option (0.1.1)
+    terminal-table (3.0.2)
+      unicode-display_width (>= 1.1.1, < 3)
+    trailblazer-option (0.1.2)
     tty-cursor (0.7.1)
     tty-screen (0.8.1)
     tty-spinner (0.9.3)
@@ -188,11 +191,11 @@ GEM
     uber (0.1.0)
     unf (0.1.4)
       unf_ext
-    unf_ext (0.0.8)
-    unicode-display_width (1.8.0)
-    webrick (1.7.0)
+    unf_ext (0.0.8.2)
+    unicode-display_width (2.4.2)
+    webrick (1.8.1)
     word_wrap (1.0.0)
-    xcodeproj (1.21.0)
+    xcodeproj (1.22.0)
       CFPropertyList (>= 2.3.3, < 4.0)
       atomos (~> 0.1.3)
       claide (>= 1.0.2, < 2.0)
@@ -206,10 +209,11 @@ GEM
 
 PLATFORMS
   arm64-darwin-21
+  arm64-darwin-22
   x86_64-darwin-19
 
 DEPENDENCIES
   fastlane
 
 BUNDLED WITH
-   2.3.26
+   2.4.19

+ 7 - 0
fastlane/Fastfile

@@ -126,6 +126,7 @@ platform :ios do
       skip_submission: false,
       ipa: "iAPS.ipa",
       skip_waiting_for_build_processing: true,
+      changelog: git_branch+" "+last_git_commit[:abbreviated_commit_hash],
     )
   end
 
@@ -203,6 +204,12 @@ platform :ios do
     end
 
     find_bundle_id("ru.artpancreas.#{TEAMID}.FreeAPS")
+
+    match(
+      type: "appstore",
+      git_basic_authorization: Base64.strict_encode64("#{GITHUB_REPOSITORY_OWNER}:#{GH_PAT}"),
+      app_identifier: [],
+    )
   end
 
   desc "Nuke Certs"